all repos — weimar @ c9643af6a7047483b00143b1304a7aff7ce61173

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

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

  1<script lang="ts">
  2	import { onMount } from 'svelte';
  3	import { uploadAvatar, getMe } from '$lib/api';
  4	import Avatar from '$lib/Avatar.svelte';
  5
  6	let file = $state<File | null>(null);
  7	let previewUrl = $state('');
  8	let username = $state('');
  9	let error = $state('');
 10	let success = $state('');
 11	let loading = $state(false);
 12
 13	onMount(async () => {
 14		try {
 15			const u = await getMe();
 16			username = u.username;
 17			if (u.avatar_url) {
 18				previewUrl = u.avatar_url;
 19			}
 20		} catch {}
 21	});
 22
 23	function handleFileSelect(e: Event) {
 24		const f = (e.target as HTMLInputElement).files?.[0];
 25		if (!f) return;
 26		file = f;
 27		previewUrl = URL.createObjectURL(f);
 28		error = '';
 29		success = '';
 30	}
 31
 32	async function handleSubmit(e: Event) {
 33		e.preventDefault();
 34		if (!file) return;
 35		error = '';
 36		success = '';
 37		loading = true;
 38		try {
 39			const result = await uploadAvatar(file);
 40			success = 'Avatar updated.';
 41			previewUrl = result.avatar_url;
 42			file = null;
 43		} catch (err) {
 44			error = err instanceof Error ? err.message : 'Upload failed';
 45		} finally {
 46			loading = false;
 47		}
 48	}
 49</script>
 50
 51<div class="page-head">
 52	<h1>SETTINGS</h1>
 53	<div class="head-line"></div>
 54</div>
 55
 56<form onsubmit={handleSubmit}>
 57	{#if error}
 58		<p class="error">{error}</p>
 59	{/if}
 60	{#if success}
 61		<p class="success">{success}</p>
 62	{/if}
 63
 64	<div class="avatar-section">
 65		<div class="avatar-preview">
 66			<Avatar src={previewUrl} username={username} size={100} />
 67		</div>
 68
 69		<label>
 70			<span class="label-text">AVATAR IMAGE</span>
 71			<div class="file-wrap">
 72				<input type="file" id="avatar-input" accept="image/*"
 73					onchange={handleFileSelect}
 74					disabled={loading}
 75				/>
 76				<label for="avatar-input" class="file-label">
 77					{file ? file.name : 'CHOOSE IMAGE'}
 78				</label>
 79			</div>
 80		</label>
 81	</div>
 82
 83	<button type="submit" class="btn" disabled={loading || !file}>
 84		{loading ? 'UPLOADING...' : 'UPLOAD AVATAR'}
 85	</button>
 86</form>
 87
 88<style>
 89	.page-head {
 90		margin-bottom: 1.5rem;
 91	}
 92	.page-head h1 {
 93		margin: 0;
 94	}
 95	.head-line {
 96		height: 3px;
 97		width: 3rem;
 98		background: #c62828;
 99		margin-top: 0.4rem;
100	}
101
102	form {
103		display: flex;
104		flex-direction: column;
105		gap: 1rem;
106		max-width: 400px;
107	}
108
109	.error {
110		color: #c62828;
111		font-family: 'Inter', sans-serif;
112		font-size: 0.85rem;
113		font-weight: 600;
114	}
115	.success {
116		color: #2e7d32;
117		font-family: 'Inter', sans-serif;
118		font-size: 0.85rem;
119		font-weight: 600;
120	}
121
122	.avatar-section {
123		display: flex;
124		gap: 1.5rem;
125		align-items: flex-start;
126	}
127	.avatar-preview {
128		width: 100px;
129		height: 100px;
130		background: #1a1a1a;
131		flex-shrink: 0;
132		display: flex;
133		align-items: center;
134		justify-content: center;
135		overflow: hidden;
136	}
137
138	.label-text {
139		font-family: 'Oswald', sans-serif;
140		font-weight: 500;
141		font-size: 0.75rem;
142		letter-spacing: 0.15em;
143		color: #888;
144	}
145	.file-wrap {
146		position: relative;
147		margin-top: 0.25rem;
148	}
149	#avatar-input {
150		position: absolute;
151		width: 1px;
152		height: 1px;
153		opacity: 0;
154		pointer-events: none;
155	}
156	.file-label {
157		display: flex;
158		align-items: center;
159		padding: 0.55rem 0.75rem;
160		border: 2px solid #1a1a1a;
161		font-family: 'Inter', sans-serif;
162		font-size: 0.85rem;
163		font-weight: 500;
164		color: #888;
165		background: #fff;
166		cursor: pointer;
167		transition: border-color 0.15s;
168	}
169	.file-label:hover {
170		border-color: #c62828;
171	}
172
173	.btn {
174		display: inline-flex;
175		align-items: center;
176		justify-content: center;
177		padding: 0.6rem 1.5rem;
178		background: #1a1a1a;
179		color: #fff;
180		border: none;
181		font-family: 'Oswald', sans-serif;
182		font-weight: 500;
183		font-size: 0.85rem;
184		letter-spacing: 0.1em;
185		cursor: pointer;
186		text-transform: uppercase;
187		transition: background 0.15s;
188		align-self: flex-start;
189	}
190	.btn:hover {
191		background: #c62828;
192	}
193	.btn:disabled {
194		opacity: 0.4;
195		cursor: default;
196	}
197</style>