Skip to content

Commit

Permalink
fix(login): reduce complexity (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
Friskopp authored Dec 6, 2024
1 parent 93950e1 commit 4a49513
Show file tree
Hide file tree
Showing 13 changed files with 111 additions and 782 deletions.
6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
"eslint": "^9.14.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "2.46.0",
"formsnap": "2.0.0-next.1",
"globals": "^15.12.0",
"lucide-svelte": "^0.452.0",
"postcss": "^8.4.47",
Expand All @@ -53,13 +52,12 @@
"svelte-check": "4.0.4",
"svelte-eslint-parser": "0.43.0",
"svelte-preprocess": "6.0.3",
"sveltekit-superforms": "^2.21.1",
"tailwindcss": "3.4.13",
"tslib": "2.7.0",
"typescript": "5.6.3",
"typescript-eslint": "^8.13.0",
"vite": "5.4.8",
"zod": "^3.23.8"
"valibot": "1.0.0-beta.9",
"vite": "5.4.8"
},
"packageManager": "[email protected]+sha512.cce0f9de9c5a7c95bef944169cc5dfe8741abfb145078c0d508b868056848a87c81e626246cb60967cbd7fd29a6c062ef73ff840d96b3c86c40ac92cf4a813ee"
}
450 changes: 3 additions & 447 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

218 changes: 92 additions & 126 deletions src/components/login/Login.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<script lang="ts">
import * as Form from '$lib/components/ui/form/index.js';
import { zodClient } from 'sveltekit-superforms/adapters';
import { type Infer, superForm, type ValidationErrors } from 'sveltekit-superforms';
import { loginSchema, type LoginSchema } from './schema';
import { safeParse, flatten } from 'valibot';
import { open } from '@tauri-apps/plugin-shell';
import { onDestroy, onMount } from 'svelte';
import { getServerPort } from '$lib/app';
Expand All @@ -13,27 +11,22 @@
import { cn } from '$lib/utils';
import { Input } from '$lib/components/ui/input';
import { getAuthContext } from '$lib/stores/contexts';
import Label from '$lib/components/ui/label/label.svelte';
let ghCtx = getAuthContext();
const defaultHost = 'github.com';
let token = $state('');
let hostname = $state(defaultHost);
const formData = $derived({ token, hostname });
let fieldErrors = $state<{ [x: string]: [string, ...string[]] } | undefined>(undefined);
let loading = $state(false);
let processing = $state(false);
let port: number;
let unlistenFn: () => void;
const form = superForm(
{
token: '',
hostname: defaultHost,
},
{
validators: zodClient(loginSchema),
}
);
let fieldErrors: ValidationErrors<Infer<LoginSchema>> = $state({});
function handleToken() {
open(createAuthURL(port));
}
Expand Down Expand Up @@ -68,129 +61,102 @@
unlistenFn();
});
const { form: formData } = form;
const handleSubmit = async () => {
const { issues, success } = safeParse<LoginSchema>(loginSchema, formData);
const handleSubmit = async (e: SubmitEvent) => {
e.preventDefault();
const { errors, valid } = await form.validateForm();
fieldErrors = issues ? flatten<LoginSchema>(issues).nested : undefined;
if (valid) {
if (success) {
loading = true;
try {
await ghCtx.signIn(form.capture().data);
await ghCtx.signIn(formData);
} finally {
loading = false;
}
} else {
fieldErrors = errors;
}
};
</script>

<div class="m-8">
<form onsubmit={handleSubmit}>
<Form.Field {form} name="token">
<Form.Control>
{#snippet children({ props })}
<div class="m-0 flex flex-row justify-between">
<Form.Label class={cn(fieldErrors.token?.length && 'text-destructive')}>Token</Form.Label>
{#if fieldErrors.token}
<Form.Label class="text-destructive">{fieldErrors.token.at(-1)}</Form.Label>
{/if}
</div>
<Input
{...props}
class={cn(fieldErrors.token?.length && 'border-destructive')}
placeholder="The 40 characters token generated on GitHub"
bind:value={$formData.token}
<div class="flex flex-col gap-2 p-8">
<div class="flex h-4 flex-row items-center justify-between">
<Label for="token">Token</Label>
{#if fieldErrors?.token}
<p class="text-sm text-red-400 dark:text-red-300">{fieldErrors.token.at(0)}</p>
{/if}
</div>
<Input
bind:value={token}
class={cn(fieldErrors?.token && 'border-red-300')}
placeholder="The 40 characters token generated on GitHub"
/>
<span class="text-sm">
To generate a token, go to GitHub,
<button
class="cursor-pointer underline hover:text-gray-500 dark:hover:text-gray-300"
onclick={() =>
open('https://github.com/settings/tokens/new?description=gitbar&default_expires_at=none&scopes=repo,read:org')}
>
personal access tokens
</button>
</span>
<div class="flex h-4 flex-row items-center justify-between">
<Label for="hostname">Hostname</Label>
{#if fieldErrors?.hostname}
<p class="text-sm text-red-400 dark:text-red-300">{fieldErrors.hostname.at(-1)}</p>
{/if}
</div>
<Input bind:value={hostname} class={cn(fieldErrors?.hostname && 'border-red-300')} placeholder="github.company.com" />
<span class="text-sm">
Defaults to {defaultHost}. Change only if you are using GitHub for Enterprise.
</span>
<div class="flex flex-col items-center gap-1">
<Button
variant="default"
size="sm"
class={cn('w-full', loading && 'opacity-50')}
disabled={loading}
title="Submit"
onclick={handleSubmit}
>Submit {#if loading}
<svg
class="ml-3 h-4 w-4 animate-spin text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
{/snippet}
</Form.Control>
<Form.Description class="pb-1">
To generate a token, go to GitHub,
<button
class="cursor-pointer underline hover:text-gray-500 dark:hover:text-gray-300"
onclick={() =>
open(
'https://github.com/settings/tokens/new?description=gitbar&default_expires_at=none&scopes=repo,read:org'
)}
</svg>
{/if}</Button
>
OR
<Button
variant="secondary"
size="sm"
class={cn('w-full', processing && 'opacity-50')}
disabled={processing}
onclick={handleToken}
title="Login via GitHub"
>Login via GitHub
{#if processing}
<svg
class="ml-3 h-4 w-4 animate-spin text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
personal access tokens
</button></Form.Description
>
</Form.Field>

<Form.Field {form} name="hostname">
<Form.Control>
{#snippet children({ props })}
<div class="m-0 flex flex-row justify-between">
<Form.Label class={cn(fieldErrors.hostname?.length && 'text-destructive')}>Hostname</Form.Label>
{#if fieldErrors.hostname}
<Form.Label class="text-destructive">{fieldErrors.hostname.at(-1)}</Form.Label>
{/if}
</div>
<Input
{...props}
placeholder="github.company.com"
bind:value={$formData.hostname}
class={cn(fieldErrors.hostname?.length && 'border-destructive')}
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
{/snippet}
</Form.Control>
<Form.Description class="pb-1">
Defaults to {defaultHost}. Change only if you are using GitHub for Enterprise.
</Form.Description>
</Form.Field>

<div class="flex flex-col items-center gap-1">
<Button
variant="default"
size="sm"
class={cn('w-full', loading && 'opacity-50')}
disabled={loading}
title="Submit"
type="submit"
>Submit {#if loading}
<svg
class="ml-3 h-4 w-4 animate-spin text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
{/if}</Button
>
OR
<Button
variant="secondary"
size="sm"
class={cn('w-full', processing && 'opacity-50')}
disabled={processing}
onclick={handleToken}
title="Login via GitHub"
>Login via GitHub
{#if processing}
<svg
class="ml-3 h-4 w-4 animate-spin text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
{/if}
</Button>
</div>
</form>
</svg>
{/if}
</Button>
</div>
</div>
26 changes: 14 additions & 12 deletions src/components/login/schema.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { z } from 'zod';
import { object, string, regex, minLength, maxLength, pipe } from 'valibot';

export const loginSchema = z.object({
token: z
.string()
.min(40) // GitHub PATs are at least 40 characters
.max(255) // Setting a reasonable maximum length
.regex(/^ghp_[A-Za-z0-9]{36}$/, "Must be a valid token starting with 'ghp_'"),
hostname: z
.string()
.min(4) // Minimum length for a valid hostname
.max(255) // Maximum length for a hostname
.regex(/^github\.(com|[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+)$/, 'Must be a valid hostname'),
export const loginSchema = object({
token: pipe(
string(),
minLength(40, 'Token must be at least 40 characters long'),
maxLength(255),
regex(/^ghp_[A-Za-z0-9]{36}$/, "Must be a valid token starting with 'ghp_'")
),
hostname: pipe(
string(),
minLength(4),
maxLength(255),
regex(/^github\.(com|[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+)$/, 'Must be a valid hostname')
),
});

export type LoginSchema = typeof loginSchema;
7 changes: 0 additions & 7 deletions src/lib/components/ui/form/form-button.svelte

This file was deleted.

13 changes: 0 additions & 13 deletions src/lib/components/ui/form/form-description.svelte

This file was deleted.

29 changes: 0 additions & 29 deletions src/lib/components/ui/form/form-element-field.svelte

This file was deleted.

27 changes: 0 additions & 27 deletions src/lib/components/ui/form/form-field-errors.svelte

This file was deleted.

Loading

0 comments on commit 4a49513

Please sign in to comment.