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