13 Commits

Author SHA1 Message Date
fac25ca67b refactor: remove signin step and related buttons from signup process 2025-04-07 13:25:42 +02:00
d11f22f5f8 larger qr code 2025-03-24 11:01:22 +01:00
42f2a5ba21 hide signup progress on first step 2025-03-24 11:01:17 +01:00
31cd1a61ad hide darkmode toggle for now 2025-03-24 11:01:02 +01:00
9f63e3a77a theme 2025-03-24 10:45:21 +01:00
4c5499d050 drop the countdown for now 2025-03-24 10:45:14 +01:00
497a108647 wip 2025-03-24 10:45:03 +01:00
38e6c145e2 sample qr code 2025-03-24 10:31:58 +01:00
828cc0547e agb check state 2025-03-24 10:29:06 +01:00
c3248099c6 autofocus + more signup logic 2025-03-24 10:26:05 +01:00
2aa8c4178a more 2025-03-23 00:53:46 +01:00
52b8db4a5a more flow 2025-03-23 00:51:23 +01:00
012a0043b3 feat: final state 2025-03-23 00:48:58 +01:00
9 changed files with 225 additions and 115 deletions

View File

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

12
pnpm-lock.yaml generated
View File

@@ -48,6 +48,9 @@ importers:
globals:
specifier: ^16.0.0
version: 16.0.0
lucide-svelte:
specifier: ^0.483.0
version: 0.483.0(svelte@5.25.2)
svelte:
specifier: ^5.0.0
version: 5.25.2
@@ -1002,6 +1005,11 @@ packages:
lru-cache@10.4.3:
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:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
@@ -2332,6 +2340,10 @@ snapshots:
lru-cache@10.4.3: {}
lucide-svelte@0.483.0(svelte@5.25.2):
dependencies:
svelte: 5.25.2
magic-string@0.30.17:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0

View File

@@ -6,73 +6,44 @@
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 222.2 47.4% 11.2%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 72.2% 50.6%;
--destructive: 0 72.22% 50.59%;
--destructive-foreground: 210 40% 98%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.75rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--primary: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 48%;
}
}

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,30 +8,26 @@
</script>
<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.Header>
<Card.Title class="text-2xl">Login</Card.Title>
<Card.Description
>Please enter credentials for an account that is allowed to create
runners and their selfservice links.</Card.Description
>
<Card.Description>Please enter kiosk credentials.</Card.Description>
</Card.Header>
<Card.Content class="grid gap-4">
<div class="grid gap-2">
<Label for="email">Email</Label>
<Input id="email" type="email" placeholder="m@example.com" required />
</div>
<div class="grid gap-2">
<Label for="password">Password</Label>
<Input id="password" type="password" required />
<Label for="password">Token</Label>
<Input id="password" type="password" required autofocus />
</div>
</Card.Content>
<Card.Footer>
<Button class="w-full" on:click={() => (loggedin = true)}
>Sign in</Button
>
<Button class="w-full" type="submit">Sign in</Button>
</Card.Footer>
</Card.Root>
</div>
</form>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

View File

@@ -1,39 +1,62 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button/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 { Label } from "$lib/components/ui/label";
import { Progress } from "$lib/components/ui/progress";
import sampleQR from "./sampleqrlfkselfservice.png";
const Steps = {
WELCOME: {
name: "Welcome",
progress: 0,
showback: false,
},
SIGNUP: {
name: "Signup",
progress: 25,
showback: true,
},
SIGNUP_MAIL: {
name: "Signup with Mail",
progress: 50,
showback: true,
},
SIGNUP_AGB_ACCEPT: {
name: "Accept terms",
progress: 75,
showback: true,
},
SIGNUP_SUCCESS: {
name: "Done",
progress: 100,
showback: false,
},
SIGNIN: {
name: "Signin",
progress: 50,
},
SIGNIN_BARCODE: {
name: "Signin with Barcode",
progress: 100,
showback: false,
},
SIGNIN_MAIL: {
name: "Signin by Mail",
progress: 75,
showback: true,
},
SIGNIN_MAIL_SUCCESS: {
name: "Signin by Mail Success",
progress: 100,
showback: false,
},
} as const;
type StepType = (typeof Steps)[keyof typeof Steps];
let currentStep = $state<StepType>(Steps.WELCOME);
let agbAgreed = $state<boolean>(false);
let email = $state<string>("");
let firstname = $state<string>("");
let lastname = $state<string>("");
let remainingseconds = $state<number>(5);
</script>
<div class="pt-48">
@@ -44,12 +67,15 @@
<Card.Description
>Let me guide you through the signup process.</Card.Description
>
<Progress value={currentStep.progress} />
{#if currentStep.name !== Steps.WELCOME.name}
<Progress value={currentStep.progress} />
{/if}
</Card.Header>
<Card.Content class="grid gap-4">
{#if currentStep.name == Steps.WELCOME.name}
<div class="grid gap-2">
<Button
autofocus
class="w-full"
on:click={() => {
currentStep = Steps.SIGNUP;
@@ -66,53 +92,102 @@
>
</div>
{:else if currentStep.name == Steps.SIGNUP.name}
<div class="grid gap-2">
<Label for="firstname">Please enter your first name</Label>
<Input id="firstname" type="text" placeholder="Max" required />
</div>
<div class="grid gap-2">
<Label for="lastname">Please enter your last name</Label>
<Input
id="lastname"
type="text"
placeholder="Mustermann"
required
/>
<form
class="grid gap-2"
onsubmit={() => {
currentStep = Steps.SIGNUP_MAIL;
}}
>
<div class="grid gap-2">
<Label for="firstname">Please enter your first name</Label>
<Input
on:keypress={(event) => {
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
>
</form>
{:else if currentStep.name == Steps.SIGNUP_MAIL.name}
<form
class="grid gap-2"
onsubmit={() => {
currentStep = Steps.SIGNUP_AGB_ACCEPT;
}}
>
<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 bind:checked={agbAgreed} 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
disabled={!agbAgreed}
variant="default"
class="w-full"
on:click={() => {
currentStep = Steps.SIGNUP_MAIL;
currentStep = Steps.SIGNUP_SUCCESS;
}}>Continue</Button
>
{:else if currentStep.name == Steps.SIGNUP_MAIL.name}
<div class="grid gap-2">
<Label for="email"
>Enter your email to get an invitation to our selfservice
(optional)</Label
>
<Input id="email" type="email" placeholder="m@example.com" />
</div>
{:else if currentStep.name == Steps.SIGNUP_SUCCESS.name}
<p class="text-center">
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.
<!-- TODO: generate actual qr code -->
<img class="mx-auto h-56" src={sampleQR} alt="" />
</p>
{:else if currentStep.name == Steps.SIGNIN.name}
<div class="grid gap-2">
<Button
class="w-full"
on:click={() => {
currentStep = Steps.SIGNIN_BARCODE;
}}>I brought my Barcode</Button
>
</div>
<div class="grid gap-2">
<Button
variant="secondary"
class="w-full"
on:click={() => {
currentStep = Steps.SIGNIN_MAIL;
}}>I don't have my barcode with me</Button
>
</div>
{:else if currentStep.name == Steps.SIGNIN_BARCODE.name}
Just walk up to the next available person and show them your barcode.
{:else if currentStep.name == Steps.SIGNIN_MAIL.name}
<div class="grid gap-2">
@@ -123,6 +198,7 @@
id="email"
type="email"
placeholder="m@example.com"
bind:value={email}
required
/>
</div>
@@ -130,14 +206,18 @@
<Button
class="w-full"
on:click={() => {
currentStep = Steps.SIGNIN_BARCODE;
}}>Next!</Button
console.log("Email: ", email);
currentStep = Steps.SIGNIN_MAIL_SUCCESS;
}}>Continue</Button
>
</div>
{: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.
{/if}
</Card.Content>
<Card.Footer>
{#if currentStep.name != Steps.WELCOME.name}
<Card.Footer class="grid gap-2">
{#if currentStep.showback}
<Button
variant="destructive"
class="w-full"
@@ -149,12 +229,12 @@
case Steps.SIGNUP_MAIL.name:
currentStep = Steps.SIGNUP;
break;
case Steps.SIGNUP_AGB_ACCEPT.name:
currentStep = Steps.SIGNUP_MAIL;
break;
case Steps.SIGNIN.name:
currentStep = Steps.WELCOME;
break;
case Steps.SIGNIN_BARCODE.name:
currentStep = Steps.SIGNIN;
break;
case Steps.SIGNIN_MAIL.name:
currentStep = Steps.SIGNIN;
break;
@@ -164,6 +244,15 @@
}}>Go Back</Button
>
{/if}
{#if currentStep.progress == 100}
<Button
variant="default"
class="w-full"
on:click={() => {
currentStep = Steps.WELCOME;
}}>Done</Button
>
{/if}
</Card.Footer>
</Card.Root>
</div>

View File

@@ -18,7 +18,7 @@
<div class="container flex items-center justify-between py-2">
<h1 class="text-xl font-bold">Lauf für Kaya! Kiosk</h1>
<div class="flex items-center gap-2">
<Button on:click={toggleMode} variant="outline" size="icon">
<!-- <Button on:click={toggleMode} variant="outline" size="icon">
<Sun
class="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
/>
@@ -26,7 +26,7 @@
class="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
/>
<span class="sr-only">Toggle theme</span>
</Button>
</Button> -->
<Button
variant="outline"
size="icon"