@leadertechie/personal-site-kit 0.1.0-alpha.4 → 0.1.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,8 +3,6 @@ import { customElement, state } from 'lit/decorators.js';
3
3
 
4
4
  import { adminStyles } from './styles';
5
5
 
6
- console.log('[AdminPortal] Module loading');
7
-
8
6
  interface ContentItem {
9
7
  key: string;
10
8
  size: number;
@@ -19,17 +17,23 @@ interface StaticDetails {
19
17
  email?: string;
20
18
  }
21
19
 
22
- console.log('[AdminPortal] About to define custom element');
20
+ interface AuthStatus {
21
+ configured: boolean;
22
+ username: string | null;
23
+ }
23
24
 
24
25
  @customElement('admin-portal')
25
26
  export class AdminPortal extends LitElement {
26
27
  static styles = adminStyles;
27
28
 
28
29
  @state()
29
- accessor apiKey = '';
30
+ accessor isAuthenticated = false;
30
31
 
31
32
  @state()
32
- accessor isAuthenticated = false;
33
+ accessor isSetup = false;
34
+
35
+ @state()
36
+ accessor isLoading = true;
33
37
 
34
38
  @state()
35
39
  accessor contentList: ContentItem[] = [];
@@ -43,26 +47,157 @@ export class AdminPortal extends LitElement {
43
47
  @state()
44
48
  accessor staticDetails: StaticDetails = {};
45
49
 
50
+ @state()
51
+ accessor loginError = '';
52
+
46
53
  get apiUrl() {
47
54
  return (window as any).__VITE_API_URL__ || import.meta.env.VITE_API_URL || 'http://localhost:8787';
48
55
  }
49
56
 
50
- handleLogin(e: Event) {
57
+ async connectedCallback() {
58
+ super.connectedCallback();
59
+ await this.checkAuthStatus();
60
+ }
61
+
62
+ async checkAuthStatus() {
63
+ try {
64
+ const res = await fetch(`${this.apiUrl}/auth/status`, {
65
+ credentials: 'include'
66
+ });
67
+ if (res.ok) {
68
+ const status: AuthStatus = await res.json();
69
+ this.isSetup = status.configured;
70
+
71
+ if (status.configured) {
72
+ await this.tryAutoLogin();
73
+ } else {
74
+ this.isLoading = false;
75
+ }
76
+ } else {
77
+ this.isSetup = false;
78
+ this.isLoading = false;
79
+ }
80
+ } catch (e) {
81
+ this.isSetup = false;
82
+ this.isLoading = false;
83
+ }
84
+ }
85
+
86
+ async tryAutoLogin() {
87
+ try {
88
+ const res = await fetch(`${this.apiUrl}/content`, {
89
+ credentials: 'include'
90
+ });
91
+ if (res.ok) {
92
+ this.contentList = await res.json();
93
+ this.isAuthenticated = true;
94
+ }
95
+ } catch (e) {}
96
+ this.isLoading = false;
97
+ }
98
+
99
+ async handleLogin(e: Event) {
100
+ e.preventDefault();
101
+ this.loginError = '';
102
+
103
+ const usernameInput = this.shadowRoot?.querySelector('#username') as HTMLInputElement;
104
+ const passwordInput = this.shadowRoot?.querySelector('#password') as HTMLInputElement;
105
+
106
+ if (!usernameInput?.value || !passwordInput?.value) {
107
+ this.loginError = 'Username and password required';
108
+ return;
109
+ }
110
+
111
+ try {
112
+ const res = await fetch(`${this.apiUrl}/auth/login`, {
113
+ method: 'POST',
114
+ credentials: 'include',
115
+ headers: { 'Content-Type': 'application/json' },
116
+ body: JSON.stringify({
117
+ username: usernameInput.value,
118
+ password: passwordInput.value
119
+ })
120
+ });
121
+
122
+ if (res.ok) {
123
+ this.isAuthenticated = true;
124
+ await this.fetchContent();
125
+ } else {
126
+ const data = await res.json();
127
+ this.loginError = data.error || 'Login failed';
128
+ }
129
+ } catch (e) {
130
+ this.loginError = 'Connection error';
131
+ }
132
+ }
133
+
134
+ async handleSetup(e: Event) {
51
135
  e.preventDefault();
52
- const input = this.shadowRoot?.querySelector('#apiKey') as HTMLInputElement;
53
- if (input.value) {
54
- this.apiKey = input.value;
55
- this.isAuthenticated = true;
56
- this.fetchContent();
136
+ this.loginError = '';
137
+
138
+ const usernameInput = this.shadowRoot?.querySelector('#username') as HTMLInputElement;
139
+ const passwordInput = this.shadowRoot?.querySelector('#password') as HTMLInputElement;
140
+ const confirmInput = this.shadowRoot?.querySelector('#confirmPassword') as HTMLInputElement;
141
+
142
+ if (!usernameInput?.value || !passwordInput?.value) {
143
+ this.loginError = 'Username and password required';
144
+ return;
145
+ }
146
+
147
+ if (usernameInput.value.length < 3) {
148
+ this.loginError = 'Username must be at least 3 characters';
149
+ return;
57
150
  }
151
+
152
+ if (passwordInput.value.length < 8) {
153
+ this.loginError = 'Password must be at least 8 characters';
154
+ return;
155
+ }
156
+
157
+ if (passwordInput.value !== confirmInput?.value) {
158
+ this.loginError = 'Passwords do not match';
159
+ return;
160
+ }
161
+
162
+ try {
163
+ const res = await fetch(`${this.apiUrl}/auth/setup`, {
164
+ method: 'POST',
165
+ credentials: 'include',
166
+ headers: { 'Content-Type': 'application/json' },
167
+ body: JSON.stringify({
168
+ username: usernameInput.value,
169
+ password: passwordInput.value
170
+ })
171
+ });
172
+
173
+ if (res.ok) {
174
+ this.isAuthenticated = true;
175
+ this.isSetup = true;
176
+ await this.fetchContent();
177
+ } else {
178
+ const data = await res.json();
179
+ this.loginError = data.error || 'Setup failed';
180
+ }
181
+ } catch (e) {
182
+ this.loginError = 'Connection error';
183
+ }
184
+ }
185
+
186
+ async handleLogout() {
187
+ try {
188
+ await fetch(`${this.apiUrl}/auth/logout`, {
189
+ method: 'POST',
190
+ credentials: 'include'
191
+ });
192
+ } catch (e) {}
193
+ this.isAuthenticated = false;
194
+ this.contentList = [];
58
195
  }
59
196
 
60
197
  async fetchContent() {
61
198
  try {
62
199
  const res = await fetch(`${this.apiUrl}/content`, {
63
- headers: {
64
- 'Authorization': `Bearer ${this.apiKey}`
65
- }
200
+ credentials: 'include'
66
201
  });
67
202
  if (res.ok) {
68
203
  this.contentList = await res.json();
@@ -76,13 +211,13 @@ export class AdminPortal extends LitElement {
76
211
 
77
212
  async fetchStaticDetails() {
78
213
  try {
79
- const res = await fetch(`${this.apiUrl}/api/static`);
214
+ const res = await fetch(`${this.apiUrl}/static`, {
215
+ credentials: 'include'
216
+ });
80
217
  if (res.ok) {
81
218
  this.staticDetails = await res.json();
82
219
  }
83
- } catch (e) {
84
- // Ignore errors
85
- }
220
+ } catch (e) {}
86
221
  }
87
222
 
88
223
  async handleUpload(key: string, file: File) {
@@ -90,9 +225,7 @@ export class AdminPortal extends LitElement {
90
225
  this.statusMessage = 'Uploading...';
91
226
  const res = await fetch(`${this.apiUrl}/content/${key}`, {
92
227
  method: 'PUT',
93
- headers: {
94
- 'Authorization': `Bearer ${this.apiKey}`
95
- },
228
+ credentials: 'include',
96
229
  body: file
97
230
  });
98
231
 
@@ -112,9 +245,7 @@ export class AdminPortal extends LitElement {
112
245
  this.statusMessage = 'Clearing cache...';
113
246
  const res = await fetch(`${this.apiUrl}/cache-clear`, {
114
247
  method: 'POST',
115
- headers: {
116
- 'Authorization': `Bearer ${this.apiKey}`
117
- }
248
+ credentials: 'include'
118
249
  });
119
250
 
120
251
  if (res.ok) {
@@ -133,9 +264,7 @@ export class AdminPortal extends LitElement {
133
264
  try {
134
265
  const res = await fetch(`${this.apiUrl}/content/${key}`, {
135
266
  method: 'DELETE',
136
- headers: {
137
- 'Authorization': `Bearer ${this.apiKey}`
138
- }
267
+ credentials: 'include'
139
268
  });
140
269
 
141
270
  if (res.ok) {
@@ -156,6 +285,41 @@ export class AdminPortal extends LitElement {
156
285
  return this.contentList.filter(c => c.key.startsWith(prefix));
157
286
  }
158
287
 
288
+ renderLoginForm() {
289
+ return html`
290
+ <div class="container">
291
+ <div class="login-box">
292
+ <h2>Admin Setup</h2>
293
+ <p>Create your admin credentials</p>
294
+ <form @submit=${this.handleSetup}>
295
+ <input type="text" id="username" placeholder="Username (3+ chars)" />
296
+ <input type="password" id="password" placeholder="Password (8+ chars)" />
297
+ <input type="password" id="confirmPassword" placeholder="Confirm Password" />
298
+ ${this.loginError ? html`<div class="error-message">${this.loginError}</div>` : ''}
299
+ <button type="submit" class="btn-primary">Create Account</button>
300
+ </form>
301
+ </div>
302
+ </div>
303
+ `;
304
+ }
305
+
306
+ renderLogin() {
307
+ return html`
308
+ <div class="container">
309
+ <div class="login-box">
310
+ <h2>Admin Login</h2>
311
+ <p>Enter your credentials</p>
312
+ <form @submit=${this.handleLogin}>
313
+ <input type="text" id="username" placeholder="Username" autocomplete="username" />
314
+ <input type="password" id="password" placeholder="Password" autocomplete="current-password" />
315
+ ${this.loginError ? html`<div class="error-message">${this.loginError}</div>` : ''}
316
+ <button type="submit" class="btn-primary">Login</button>
317
+ </form>
318
+ </div>
319
+ </div>
320
+ `;
321
+ }
322
+
159
323
  renderHomeSection() {
160
324
  const home = this.getContent('home.md');
161
325
  return html`
@@ -361,69 +525,6 @@ export class AdminPortal extends LitElement {
361
525
  `;
362
526
  }
363
527
 
364
- render() {
365
- if (!this.isAuthenticated) {
366
- return html`
367
- <div class="container">
368
- <div class="login-box">
369
- <h2>Admin Login</h2>
370
- <p>Enter your API key to manage content</p>
371
- <form @submit=${this.handleLogin}>
372
- <input type="password" id="apiKey" placeholder="API Key" />
373
- <button type="submit" class="btn-primary">Login</button>
374
- </form>
375
- </div>
376
- </div>
377
- `;
378
- }
379
-
380
- return html`
381
- <div class="container">
382
- <div class="header">
383
- <h1>Content Manager</h1>
384
- <button class="btn-secondary" @click=${() => this.handleClearCache()}>Clear Cache</button>
385
- </div>
386
-
387
- <div class="nav-tabs">
388
- <button class="nav-tab ${this.activeSection === 'home' ? 'active' : ''}"
389
- @click=${() => this.activeSection = 'home'}>Home</button>
390
- <button class="nav-tab ${this.activeSection === 'profile' ? 'active' : ''}"
391
- @click=${() => this.activeSection = 'profile'}>Profile</button>
392
- <button class="nav-tab ${this.activeSection === 'aboutme' ? 'active' : ''}"
393
- @click=${() => this.activeSection = 'aboutme'}>About Me</button>
394
- <button class="nav-tab ${this.activeSection === 'blogs' ? 'active' : ''}"
395
- @click=${() => this.activeSection = 'blogs'}>Blogs</button>
396
- <button class="nav-tab ${this.activeSection === 'stories' ? 'active' : ''}"
397
- @click=${() => this.activeSection = 'stories'}>Stories</button>
398
- <button class="nav-tab ${this.activeSection === 'images' ? 'active' : ''}"
399
- @click=${() => this.activeSection = 'images'}>Images</button>
400
- <button class="nav-tab ${this.activeSection === 'logo' ? 'active' : ''}"
401
- @click=${() => this.activeSection = 'logo'}>Logo</button>
402
- <button class="nav-tab ${this.activeSection === 'static' ? 'active' : ''}"
403
- @click=${() => {
404
- this.activeSection = 'static';
405
- this.fetchStaticDetails();
406
- }}>Site Settings</button>
407
- </div>
408
-
409
- ${this.statusMessage ? html`
410
- <div class="status-message ${this.statusMessage.includes('successful') || this.statusMessage.includes('cleared') ? 'success' : this.statusMessage.includes('failed') || this.statusMessage.includes('Error') ? 'error' : ''}">
411
- ${this.statusMessage}
412
- </div>
413
- ` : ''}
414
-
415
- ${this.activeSection === 'home' ? this.renderHomeSection() : ''}
416
- ${this.activeSection === 'profile' ? this.renderProfileSection() : ''}
417
- ${this.activeSection === 'aboutme' ? this.renderAboutMeSection() : ''}
418
- ${this.activeSection === 'blogs' ? this.renderBlogsSection() : ''}
419
- ${this.activeSection === 'stories' ? this.renderStoriesSection() : ''}
420
- ${this.activeSection === 'images' ? this.renderImagesSection() : ''}
421
- ${this.activeSection === 'logo' ? this.renderLogoSection() : ''}
422
- ${this.activeSection === 'static' ? this.renderStaticSection() : ''}
423
- </div>
424
- `;
425
- }
426
-
427
528
  renderStaticSection() {
428
529
  return html`
429
530
  <div class="section">
@@ -473,7 +574,8 @@ export class AdminPortal extends LitElement {
473
574
  const url = `${this.apiUrl}/content/staticdetails.json`;
474
575
  const res = await fetch(url, {
475
576
  method: 'PUT',
476
- headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' },
577
+ credentials: 'include',
578
+ headers: { 'Content-Type': 'application/json' },
477
579
  body: JSON.stringify(data)
478
580
  });
479
581
  if (res.ok) {
@@ -489,4 +591,65 @@ export class AdminPortal extends LitElement {
489
591
  </div>
490
592
  `;
491
593
  }
594
+
595
+ render() {
596
+ if (this.isLoading) {
597
+ return html`<div class="container"><div class="loading">Loading...</div></div>`;
598
+ }
599
+
600
+ if (!this.isSetup) {
601
+ return this.renderLoginForm();
602
+ }
603
+
604
+ if (!this.isAuthenticated) {
605
+ return this.renderLogin();
606
+ }
607
+
608
+ return html`
609
+ <div class="container">
610
+ <div class="header">
611
+ <h1>Content Manager</h1>
612
+ <button class="btn-secondary" @click=${() => this.handleLogout()}>Logout</button>
613
+ <button class="btn-secondary" @click=${() => this.handleClearCache()}>Clear Cache</button>
614
+ </div>
615
+
616
+ <div class="nav-tabs">
617
+ <button class="nav-tab ${this.activeSection === 'home' ? 'active' : ''}"
618
+ @click=${() => this.activeSection = 'home'}>Home</button>
619
+ <button class="nav-tab ${this.activeSection === 'profile' ? 'active' : ''}"
620
+ @click=${() => this.activeSection = 'profile'}>Profile</button>
621
+ <button class="nav-tab ${this.activeSection === 'aboutme' ? 'active' : ''}"
622
+ @click=${() => this.activeSection = 'aboutme'}>About Me</button>
623
+ <button class="nav-tab ${this.activeSection === 'blogs' ? 'active' : ''}"
624
+ @click=${() => this.activeSection = 'blogs'}>Blogs</button>
625
+ <button class="nav-tab ${this.activeSection === 'stories' ? 'active' : ''}"
626
+ @click=${() => this.activeSection = 'stories'}>Stories</button>
627
+ <button class="nav-tab ${this.activeSection === 'images' ? 'active' : ''}"
628
+ @click=${() => this.activeSection = 'images'}>Images</button>
629
+ <button class="nav-tab ${this.activeSection === 'logo' ? 'active' : ''}"
630
+ @click=${() => this.activeSection = 'logo'}>Logo</button>
631
+ <button class="nav-tab ${this.activeSection === 'static' ? 'active' : ''}"
632
+ @click=${() => {
633
+ this.activeSection = 'static';
634
+ this.fetchStaticDetails();
635
+ }}>Site Settings</button>
636
+ </div>
637
+
638
+ ${this.statusMessage ? html`
639
+ <div class="status-message ${this.statusMessage.includes('successful') || this.statusMessage.includes('cleared') ? 'success' : this.statusMessage.includes('failed') || this.statusMessage.includes('Error') ? 'error' : ''}">
640
+ ${this.statusMessage}
641
+ </div>
642
+ ` : ''}
643
+
644
+ ${this.activeSection === 'home' ? this.renderHomeSection() : ''}
645
+ ${this.activeSection === 'profile' ? this.renderProfileSection() : ''}
646
+ ${this.activeSection === 'aboutme' ? this.renderAboutMeSection() : ''}
647
+ ${this.activeSection === 'blogs' ? this.renderBlogsSection() : ''}
648
+ ${this.activeSection === 'stories' ? this.renderStoriesSection() : ''}
649
+ ${this.activeSection === 'images' ? this.renderImagesSection() : ''}
650
+ ${this.activeSection === 'logo' ? this.renderLogoSection() : ''}
651
+ ${this.activeSection === 'static' ? this.renderStaticSection() : ''}
652
+ </div>
653
+ `;
654
+ }
492
655
  }