autofocus + more signup logic

This commit is contained in:
Philipp Dormann 2025-03-24 10:26:05 +01:00
parent 2aa8c4178a
commit c3248099c6
Signed by: philipp
GPG Key ID: 3BB9ADD52DCA4314
6 changed files with 155 additions and 48 deletions

View File

@ -24,6 +24,7 @@
"eslint": "^9.18.0", "eslint": "^9.18.0",
"eslint-plugin-svelte": "^3.0.0", "eslint-plugin-svelte": "^3.0.0",
"globals": "^16.0.0", "globals": "^16.0.0",
"lucide-svelte": "^0.483.0",
"svelte": "^5.0.0", "svelte": "^5.0.0",
"svelte-check": "^4.0.0", "svelte-check": "^4.0.0",
"tailwind-merge": "^3.0.2", "tailwind-merge": "^3.0.2",

12
pnpm-lock.yaml generated
View File

@ -48,6 +48,9 @@ importers:
globals: globals:
specifier: ^16.0.0 specifier: ^16.0.0
version: 16.0.0 version: 16.0.0
lucide-svelte:
specifier: ^0.483.0
version: 0.483.0(svelte@5.25.2)
svelte: svelte:
specifier: ^5.0.0 specifier: ^5.0.0
version: 5.25.2 version: 5.25.2
@ -1002,6 +1005,11 @@ packages:
lru-cache@10.4.3: lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
lucide-svelte@0.483.0:
resolution: {integrity: sha512-MyMgEVLlFfPbyodGpkB+KCpyPkpjI7EKiFw1crA92B1ZXRK5hq5vTsGWAm9Nt3GAKHunoNc5MVsq3EOCz0DZSQ==}
peerDependencies:
svelte: ^3 || ^4 || ^5.0.0-next.42
magic-string@0.30.17: magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
@ -2332,6 +2340,10 @@ snapshots:
lru-cache@10.4.3: {} lru-cache@10.4.3: {}
lucide-svelte@0.483.0(svelte@5.25.2):
dependencies:
svelte: 5.25.2
magic-string@0.30.17: magic-string@0.30.17:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/sourcemap-codec': 1.5.0

View File

@ -0,0 +1,35 @@
<script lang="ts">
import { Checkbox as CheckboxPrimitive } from "bits-ui";
import Check from "lucide-svelte/icons/check";
import Minus from "lucide-svelte/icons/minus";
import { cn } from "$lib/utils.js";
type $$Props = CheckboxPrimitive.Props;
type $$Events = CheckboxPrimitive.Events;
let className: $$Props["class"] = undefined;
export let checked: $$Props["checked"] = false;
export { className as class };
</script>
<CheckboxPrimitive.Root
class={cn(
"border-primary ring-offset-background focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground peer box-content h-4 w-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50",
className
)}
bind:checked
{...$$restProps}
on:click
>
<CheckboxPrimitive.Indicator
class={cn("flex h-4 w-4 items-center justify-center text-current")}
let:isChecked
let:isIndeterminate
>
{#if isChecked}
<Check class="h-3.5 w-3.5" />
{:else if isIndeterminate}
<Minus class="h-3.5 w-3.5" />
{/if}
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>

View File

@ -0,0 +1,6 @@
import Root from "./checkbox.svelte";
export {
Root,
//
Root as Checkbox,
};

View File

@ -8,25 +8,26 @@
</script> </script>
<div class="pt-48"> <div class="pt-48">
<div class="flex justify-center items-center overflow-hidden"> <form
onsubmit={() => {
loggedin = true;
}}
class="flex justify-center items-center overflow-hidden"
>
<Card.Root class="w-full max-w-sm"> <Card.Root class="w-full max-w-sm">
<Card.Header> <Card.Header>
<Card.Title class="text-2xl">Login</Card.Title> <Card.Title class="text-2xl">Login</Card.Title>
<Card.Description <Card.Description>Please enter kiosk credentials.</Card.Description>
>Please enter kiosk credentials.</Card.Description
>
</Card.Header> </Card.Header>
<Card.Content class="grid gap-4"> <Card.Content class="grid gap-4">
<div class="grid gap-2"> <div class="grid gap-2">
<Label for="password">Token</Label> <Label for="password">Token</Label>
<Input id="password" type="password" required /> <Input id="password" type="password" required autofocus />
</div> </div>
</Card.Content> </Card.Content>
<Card.Footer> <Card.Footer>
<Button class="w-full" on:click={() => (loggedin = true)} <Button class="w-full" type="submit">Sign in</Button>
>Sign in</Button
>
</Card.Footer> </Card.Footer>
</Card.Root> </Card.Root>
</div> </form>
</div> </div>

View File

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Button } from "$lib/components/ui/button/index.js"; import { Button } from "$lib/components/ui/button/index.js";
import * as Card from "$lib/components/ui/card/index.js"; import * as Card from "$lib/components/ui/card/index.js";
import Checkbox from "$lib/components/ui/checkbox/checkbox.svelte";
import { Input } from "$lib/components/ui/input"; import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label"; import { Label } from "$lib/components/ui/label";
import { Progress } from "$lib/components/ui/progress"; import { Progress } from "$lib/components/ui/progress";
@ -21,7 +22,7 @@
progress: 50, progress: 50,
showback: true, showback: true,
}, },
SIGNUP_MAIL_ACCEPT: { SIGNUP_AGB_ACCEPT: {
name: "Accept terms", name: "Accept terms",
progress: 75, progress: 75,
showback: true, showback: true,
@ -56,6 +57,8 @@
type StepType = (typeof Steps)[keyof typeof Steps]; type StepType = (typeof Steps)[keyof typeof Steps];
let currentStep = $state<StepType>(Steps.WELCOME); let currentStep = $state<StepType>(Steps.WELCOME);
let email = $state<string>(""); let email = $state<string>("");
let firstname = $state<string>("");
let lastname = $state<string>("");
let remainingseconds = $state<number>(5); let remainingseconds = $state<number>(5);
// Function to handle automatic return to welcome screen // Function to handle automatic return to welcome screen
@ -74,7 +77,7 @@
return null; return null;
} }
import { onDestroy } from 'svelte'; import { onDestroy } from "svelte";
// Watch for changes to currentStep // Watch for changes to currentStep
$effect(() => { $effect(() => {
@ -102,6 +105,7 @@
{#if currentStep.name == Steps.WELCOME.name} {#if currentStep.name == Steps.WELCOME.name}
<div class="grid gap-2"> <div class="grid gap-2">
<Button <Button
autofocus
class="w-full" class="w-full"
on:click={() => { on:click={() => {
currentStep = Steps.SIGNUP; currentStep = Steps.SIGNUP;
@ -118,42 +122,85 @@
> >
</div> </div>
{:else if currentStep.name == Steps.SIGNUP.name} {:else if currentStep.name == Steps.SIGNUP.name}
<div class="grid gap-2"> <form
<Label for="firstname">Please enter your first name</Label> class="grid gap-2"
<Input id="firstname" type="text" placeholder="Max" required /> onsubmit={() => {
</div>
<div class="grid gap-2">
<Label for="lastname">Please enter your last name</Label>
<Input
id="lastname"
type="text"
placeholder="Mustermann"
required
/>
</div>
<Button
variant="default"
class="w-full"
on:click={() => {
currentStep = Steps.SIGNUP_MAIL; currentStep = Steps.SIGNUP_MAIL;
}}>Continue</Button }}
> >
{:else if currentStep.name == Steps.SIGNUP_MAIL.name} <div class="grid gap-2">
<div class="grid gap-2"> <Label for="firstname">Please enter your first name</Label>
<Label for="email" <Input
>Enter your email to get an invitation to our selfservice on:keypress={(event) => {
(optional)</Label console.log(event.key);
if (event.key === "Enter") {
document.getElementById("lastname")?.focus();
}
}}
bind:value={firstname}
id="firstname"
type="text"
placeholder="Max"
required
autofocus
/>
</div>
<div class="grid gap-2">
<Label for="lastname">Please enter your last name</Label>
<Input
bind:value={lastname}
id="lastname"
type="text"
placeholder="Mustermann"
required
/>
</div>
<Button type="submit" variant="default" class="w-full"
>Continue</Button
> >
<Input id="email" type="email" placeholder="m@example.com" /> </form>
</div> {:else if currentStep.name == Steps.SIGNUP_MAIL.name}
<Button <form
variant="default" class="grid gap-2"
class="w-full" onsubmit={() => {
on:click={() => { currentStep = Steps.SIGNUP_AGB_ACCEPT;
currentStep = Steps.SIGNUP_MAIL_ACCEPT; }}
}}>Continue</Button
> >
{:else if currentStep.name == Steps.SIGNUP_MAIL_ACCEPT.name} <div class="grid gap-2">
<Label for="email"
>Enter your email to get an invitation to our selfservice
(optional)</Label
>
<Input
autofocus
id="email"
type="email"
placeholder="m@example.com"
bind:value={email}
/>
</div>
<Button type="submit" variant="default" class="w-full"
>Continue</Button
>
</form>
{:else if currentStep.name == Steps.SIGNUP_AGB_ACCEPT.name}
{#if email !== ""}
By registering, you confirm that {email} is your email
{/if}
<div class="items-top flex space-x-2">
<Checkbox autofocus id="terms1" />
<div class="grid gap-1.5 leading-none">
<Label
for="terms1"
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Accept terms and conditions
</Label>
<p class="text-muted-foreground text-sm">
You agree to our Terms of Service and Privacy Policy.
</p>
</div>
</div>
<Button <Button
variant="default" variant="default"
class="w-full" class="w-full"
@ -163,13 +210,15 @@
> >
{:else if currentStep.name == Steps.SIGNUP_SUCCESS.name} {:else if currentStep.name == Steps.SIGNUP_SUCCESS.name}
<p class="text-center"> <p class="text-center">
Alright that's it, just walk up to the next available person and tell them your name.<br> Alright that's it, just walk up to the next available person and
tell them your name.<br />
You can scan this qr code to login to our selfservice. You can scan this qr code to login to our selfservice.
</p> </p>
TODO: TODO:
{:else if currentStep.name == Steps.SIGNIN.name} {:else if currentStep.name == Steps.SIGNIN.name}
<div class="grid gap-2"> <div class="grid gap-2">
<Button <Button
autofocus
class="w-full" class="w-full"
on:click={() => { on:click={() => {
currentStep = Steps.SIGNIN_BARCODE; currentStep = Steps.SIGNIN_BARCODE;
@ -210,7 +259,8 @@
> >
</div> </div>
{:else if currentStep.name == Steps.SIGNIN_MAIL_SUCCESS.name} {:else if currentStep.name == Steps.SIGNIN_MAIL_SUCCESS.name}
We found your email address, just walk up to the next available person and tell them your name. We found your email address, just walk up to the next available person
and tell them your name.
{/if} {/if}
</Card.Content> </Card.Content>
<Card.Footer class="grid gap-4"> <Card.Footer class="grid gap-4">
@ -226,6 +276,9 @@
case Steps.SIGNUP_MAIL.name: case Steps.SIGNUP_MAIL.name:
currentStep = Steps.SIGNUP; currentStep = Steps.SIGNUP;
break; break;
case Steps.SIGNUP_AGB_ACCEPT.name:
currentStep = Steps.SIGNUP_MAIL;
break;
case Steps.SIGNIN.name: case Steps.SIGNIN.name:
currentStep = Steps.WELCOME; currentStep = Steps.WELCOME;
break; break;
@ -246,9 +299,8 @@
variant="default" variant="default"
class="w-full" class="w-full"
on:click={() => { on:click={() => {
currentStep=Steps.WELCOME; currentStep = Steps.WELCOME;
} }}>Done</Button
}>Done</Button
> >
<p class="text-center"> <p class="text-center">
Returning to the home screen in {remainingseconds} seconds... Returning to the home screen in {remainingseconds} seconds...