all repos — weimar @ master

Unnamed repository; edit this file 'description' to name the repository.

web/src/routes/login/+page.svelte (view raw)

  1<script lang="ts">
  2	import { login, register } from '$lib/api';
  3
  4	let username = $state('');
  5	let password = $state('');
  6	let error = $state('');
  7	let loading = $state(false);
  8	let isRegister = $state(false);
  9
 10	async function handleSubmit(e: Event) {
 11		e.preventDefault();
 12		error = '';
 13		loading = true;
 14		try {
 15			if (isRegister) {
 16				await register(username, password);
 17			}
 18			await login(username, password);
 19			window.location.href = '/browse';
 20		} catch (err) {
 21			error = err instanceof Error ? err.message : 'Login failed';
 22		} finally {
 23			loading = false;
 24		}
 25	}
 26</script>
 27
 28<div class="page-head">
 29	<h1>{isRegister ? 'REGISTER' : 'LOGIN'}</h1>
 30	<div class="head-line"></div>
 31</div>
 32
 33<form onsubmit={handleSubmit}>
 34	{#if error}
 35		<p class="error">{error}</p>
 36	{/if}
 37	<label>
 38		<span class="label-text">USERNAME</span>
 39		<input type="text" bind:value={username} required minlength={3} disabled={loading} />
 40	</label>
 41	<label>
 42		<span class="label-text">PASSWORD</span>
 43		<input type="password" bind:value={password} required minlength={8} disabled={loading} />
 44	</label>
 45	<button type="submit" class="btn" disabled={loading}>
 46		{loading ? 'PLEASE WAIT...' : isRegister ? 'REGISTER' : 'LOGIN'}
 47	</button>
 48</form>
 49
 50<p class="toggle">
 51	<button class="link" onclick={() => { isRegister = !isRegister; error = ''; }}>
 52		{isRegister ? 'ALREADY HAVE AN ACCOUNT? LOG IN' : 'NO ACCOUNT? REGISTER'}
 53	</button>
 54</p>
 55
 56<style>
 57	.page-head {
 58		margin-bottom: 1.5rem;
 59	}
 60	.page-head h1 {
 61		margin: 0;
 62	}
 63	.head-line {
 64		height: 3px;
 65		width: 3rem;
 66		background: #c62828;
 67		margin-top: 0.4rem;
 68	}
 69
 70	form {
 71		display: flex;
 72		flex-direction: column;
 73		gap: 1rem;
 74		max-width: 340px;
 75	}
 76	label {
 77		display: flex;
 78		flex-direction: column;
 79		gap: 0.25rem;
 80	}
 81	.label-text {
 82		font-family: 'Oswald', sans-serif;
 83		font-weight: 500;
 84		font-size: 0.75rem;
 85		letter-spacing: 0.15em;
 86		color: #888;
 87	}
 88	input {
 89		padding: 0.6rem 0.75rem;
 90		border: 2px solid #1a1a1a;
 91		border-radius: 0;
 92		font-family: 'Inter', sans-serif;
 93		font-size: 0.9rem;
 94		font-weight: 500;
 95		background: #fff;
 96	}
 97	input:focus {
 98		outline: none;
 99		border-color: #c62828;
100	}
101
102	.btn {
103		display: inline-flex;
104		align-items: center;
105		justify-content: center;
106		padding: 0.6rem 1.5rem;
107		background: #1a1a1a;
108		color: #fff;
109		border: none;
110		font-family: 'Oswald', sans-serif;
111		font-weight: 500;
112		font-size: 0.85rem;
113		letter-spacing: 0.1em;
114		cursor: pointer;
115		text-transform: uppercase;
116		transition: background 0.15s;
117		align-self: flex-start;
118	}
119	.btn:hover {
120		background: #c62828;
121	}
122	.btn:disabled {
123		opacity: 0.4;
124		cursor: default;
125	}
126
127	.error {
128		color: #c62828;
129		font-family: 'Inter', sans-serif;
130		font-size: 0.85rem;
131		font-weight: 600;
132	}
133
134	.toggle {
135		margin-top: 1rem;
136	}
137	.link {
138		background: none;
139		border: none;
140		font-family: 'Oswald', sans-serif;
141		font-weight: 500;
142		font-size: 0.8rem;
143		letter-spacing: 0.08em;
144		color: #c62828;
145		cursor: pointer;
146		padding: 0;
147	}
148	.link:hover {
149		text-decoration: underline;
150	}
151</style>