@leadertechie/personal-site-kit 0.1.0-alpha.8 → 0.1.0-alpha.9

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.
Files changed (93) hide show
  1. package/dist/api/content-utils.d.ts +27 -0
  2. package/dist/api/content-utils.d.ts.map +1 -0
  3. package/dist/api/handlers/content-api.d.ts +0 -1
  4. package/dist/api/handlers/content-api.d.ts.map +1 -1
  5. package/dist/api.js +2 -2
  6. package/dist/chunks/{index-CYd_Pe2U.js → index-CnSEOZse.js} +81 -121
  7. package/dist/chunks/{template-D1uGvdWZ.js → template-DWcsZW22.js} +1 -1
  8. package/dist/chunks/{website-api-FLejlWxJ.js → website-api-BEYGOsT3.js} +90 -131
  9. package/dist/index.js +3 -3
  10. package/dist/shared.js +1 -1
  11. package/dist/ui/admin/index.d.ts +8 -0
  12. package/dist/ui/admin/index.d.ts.map +1 -1
  13. package/dist/ui.js +1 -1
  14. package/package.json +4 -4
  15. package/src/api/__tests__/info.test.ts +0 -44
  16. package/src/api/__tests__/utils.test.ts +0 -78
  17. package/src/api/handlers/about-me.ts +0 -109
  18. package/src/api/handlers/auth-handler.ts +0 -204
  19. package/src/api/handlers/auth.ts +0 -157
  20. package/src/api/handlers/content-api.ts +0 -268
  21. package/src/api/handlers/content.ts +0 -139
  22. package/src/api/handlers/home.ts +0 -79
  23. package/src/api/handlers/info.ts +0 -12
  24. package/src/api/handlers/logo.ts +0 -55
  25. package/src/api/handlers/static-details.ts +0 -48
  26. package/src/api/index.ts +0 -9
  27. package/src/api/utils.ts +0 -16
  28. package/src/api/website-api.ts +0 -142
  29. package/src/index.ts +0 -4
  30. package/src/prerender/__tests__/page-content.test.ts +0 -44
  31. package/src/prerender/__tests__/template.test.ts +0 -54
  32. package/src/prerender/data-fetcher.ts +0 -93
  33. package/src/prerender/index.ts +0 -7
  34. package/src/prerender/page-content.ts +0 -266
  35. package/src/prerender/page-generators/about.ts +0 -38
  36. package/src/prerender/page-generators/base.ts +0 -77
  37. package/src/prerender/page-generators/blog-detail.ts +0 -35
  38. package/src/prerender/page-generators/blogs-list.ts +0 -43
  39. package/src/prerender/page-generators/home.ts +0 -54
  40. package/src/prerender/page-generators/index.ts +0 -8
  41. package/src/prerender/page-generators/not-found.ts +0 -36
  42. package/src/prerender/page-generators/stories-list.ts +0 -43
  43. package/src/prerender/page-generators/story-detail.ts +0 -35
  44. package/src/prerender/prerender.ts +0 -25
  45. package/src/prerender/template.ts +0 -65
  46. package/src/prerender/website-prerender.ts +0 -152
  47. package/src/shared/config/api.ts +0 -16
  48. package/src/shared/config/index.ts +0 -43
  49. package/src/shared/config/types.ts +0 -16
  50. package/src/shared/core/__tests__/theme-toggle.test.ts +0 -204
  51. package/src/shared/core/site-store.ts +0 -38
  52. package/src/shared/core/theme-toggle.ts +0 -118
  53. package/src/shared/index.ts +0 -17
  54. package/src/shared/interfaces/ifooter-link.ts +0 -4
  55. package/src/shared/interfaces/iroute.ts +0 -4
  56. package/src/shared/models/theme-variables.css +0 -25
  57. package/src/shared/page-content.ts +0 -210
  58. package/src/shared/router.ts +0 -250
  59. package/src/shared/runtime.ts +0 -11
  60. package/src/shared/template.ts +0 -35
  61. package/src/shared/website-ui.ts +0 -92
  62. package/src/styles/markdown.css +0 -129
  63. package/src/ui/about-me/api.ts +0 -12
  64. package/src/ui/about-me/index.ts +0 -121
  65. package/src/ui/about-me/styles.ts +0 -85
  66. package/src/ui/admin/api.ts +0 -93
  67. package/src/ui/admin/components/AboutMeSection.ts +0 -47
  68. package/src/ui/admin/components/AdminSection.ts +0 -134
  69. package/src/ui/admin/components/BlogsSection.ts +0 -62
  70. package/src/ui/admin/components/HomeSection.ts +0 -47
  71. package/src/ui/admin/components/ImagesSection.ts +0 -54
  72. package/src/ui/admin/components/LoginForm.ts +0 -116
  73. package/src/ui/admin/components/LogoSection.ts +0 -51
  74. package/src/ui/admin/components/ProfileSection.ts +0 -47
  75. package/src/ui/admin/components/StaticSection.ts +0 -67
  76. package/src/ui/admin/components/StoriesSection.ts +0 -62
  77. package/src/ui/admin/components/index.ts +0 -10
  78. package/src/ui/admin/index.ts +0 -413
  79. package/src/ui/admin/styles.ts +0 -270
  80. package/src/ui/admin/types.ts +0 -26
  81. package/src/ui/banner/index.ts +0 -38
  82. package/src/ui/banner/styles.ts +0 -95
  83. package/src/ui/blog-viewer/__tests__/blogviewer.test.ts +0 -7
  84. package/src/ui/blog-viewer/index.ts +0 -127
  85. package/src/ui/blog-viewer/styles.ts +0 -23
  86. package/src/ui/footer/index.ts +0 -37
  87. package/src/ui/footer/styles.ts +0 -50
  88. package/src/ui/index.ts +0 -13
  89. package/src/ui/story-viewer/__tests__/storyviewer.test.ts +0 -7
  90. package/src/ui/story-viewer/index.ts +0 -123
  91. package/src/ui/story-viewer/styles.ts +0 -54
  92. /package/{src/shared → dist}/styles/markdown.css +0 -0
  93. /package/{src → dist}/styles/theme.css +0 -0
@@ -1,85 +0,0 @@
1
- import { css } from 'lit';
2
-
3
- export const aboutmeStyles = css`
4
- :host {
5
- display: block;
6
- }
7
-
8
- .aboutme {
9
- display: block;
10
- padding: 20px;
11
- max-width: 800px;
12
- margin: 0 auto;
13
- line-height: 1.6;
14
- }
15
-
16
- .aboutme h2 {
17
- color: var(--link-color);
18
- margin-bottom: 15px;
19
- font-size: 2em;
20
- }
21
-
22
- .aboutme h3 {
23
- color: var(--link-color);
24
- margin-bottom: 10px;
25
- }
26
-
27
- .aboutme p {
28
- margin-bottom: 10px;
29
- text-align: left;
30
- }
31
-
32
- .aboutme ul {
33
- list-style-type: disc;
34
- margin-left: 20px;
35
- text-align: left;
36
- }
37
-
38
- .aboutme li {
39
- margin-bottom: 5px;
40
- }
41
-
42
- .aboutme .profile-picture {
43
- width: 150px;
44
- height: 150px;
45
- border-radius: 50%;
46
- object-fit: cover;
47
- margin-bottom: 20px;
48
- border: 3px solid var(--link-color);
49
- box-shadow:
50
- 0 0 0 4px var(--link-color),
51
- 0 8px 16px rgba(0, 0, 0, 0.2),
52
- 0 12px 24px rgba(0, 0, 0, 0.15);
53
- }
54
-
55
- .aboutme .profile-section {
56
- text-align: center;
57
- background: var(--card-bg, var(--background-color, #fff));
58
- border-radius: 16px;
59
- padding: 2rem;
60
- box-shadow:
61
- 0 4px 6px rgba(0, 0, 0, 0.07),
62
- 0 10px 20px rgba(0, 0, 0, 0.05);
63
- margin-bottom: 2rem;
64
- }
65
-
66
- .aboutme .profile-title {
67
- color: var(--secondary-text, #666);
68
- margin-bottom: 0;
69
- text-align: center;
70
- }
71
-
72
- .aboutme h1 {
73
- margin-bottom: 0.5rem;
74
- }
75
-
76
- .aboutme .loading {
77
- text-align: center;
78
- padding: 20px;
79
- color: var(--text-color);
80
- }
81
-
82
- .aboutme .content-section {
83
- font-family: Arial, sans-serif;
84
- }
85
- `;
@@ -1,93 +0,0 @@
1
- import type { ContentItem, AuthStatus, StaticDetails } from './types';
2
-
3
- export class AdminApiService {
4
- constructor(private apiUrl: string) {}
5
-
6
- async checkAuthStatus(): Promise<AuthStatus> {
7
- const res = await fetch(`${this.apiUrl}/api/auth/status`);
8
- if (!res.ok) throw new Error('Auth status check failed');
9
- return res.json();
10
- }
11
-
12
- async tryAutoLogin(): Promise<ContentItem[]> {
13
- const res = await fetch(`${this.apiUrl}/api/content`, {
14
- credentials: 'include'
15
- });
16
- if (!res.ok) throw new Error('Auto login failed');
17
- return res.json();
18
- }
19
-
20
- async login(username: string, password: string): Promise<void> {
21
- const res = await fetch(`${this.apiUrl}/api/auth/login`, {
22
- method: 'POST',
23
- credentials: 'include',
24
- headers: { 'Content-Type': 'application/json' },
25
- body: JSON.stringify({ username, password })
26
- });
27
- if (!res.ok) {
28
- const data = await res.json();
29
- throw new Error(data.error || 'Login failed');
30
- }
31
- }
32
-
33
- async setup(username: string, password: string): Promise<void> {
34
- const res = await fetch(`${this.apiUrl}/api/auth/setup`, {
35
- method: 'POST',
36
- credentials: 'include',
37
- headers: { 'Content-Type': 'application/json' },
38
- body: JSON.stringify({ username, password })
39
- });
40
- if (!res.ok) {
41
- const data = await res.json();
42
- throw new Error(data.error || 'Setup failed');
43
- }
44
- }
45
-
46
- async logout(): Promise<void> {
47
- await fetch(`${this.apiUrl}/api/auth/logout`, {
48
- method: 'POST',
49
- credentials: 'include'
50
- });
51
- }
52
-
53
- async fetchContent(): Promise<ContentItem[]> {
54
- const res = await fetch(`${this.apiUrl}/api/content`, {
55
- credentials: 'include'
56
- });
57
- if (!res.ok) throw new Error('Failed to fetch content');
58
- return res.json();
59
- }
60
-
61
- async fetchStaticDetails(): Promise<StaticDetails> {
62
- const res = await fetch(`${this.apiUrl}/api/static`, {
63
- credentials: 'include'
64
- });
65
- if (!res.ok) throw new Error('Failed to fetch static details');
66
- return res.json();
67
- }
68
-
69
- async uploadContent(key: string, file: File): Promise<void> {
70
- const res = await fetch(`${this.apiUrl}/api/content/${key}`, {
71
- method: 'PUT',
72
- credentials: 'include',
73
- body: file
74
- });
75
- if (!res.ok) throw new Error('Upload failed');
76
- }
77
-
78
- async deleteContent(key: string): Promise<void> {
79
- const res = await fetch(`${this.apiUrl}/api/content/${key}`, {
80
- method: 'DELETE',
81
- credentials: 'include'
82
- });
83
- if (!res.ok) throw new Error('Delete failed');
84
- }
85
-
86
- async clearCache(): Promise<void> {
87
- const res = await fetch(`${this.apiUrl}/api/cache-clear`, {
88
- method: 'POST',
89
- credentials: 'include'
90
- });
91
- if (!res.ok) throw new Error('Failed to clear cache');
92
- }
93
- }
@@ -1,47 +0,0 @@
1
- import { html } from 'lit';
2
- import { customElement } from 'lit/decorators.js';
3
- import { AdminSection } from './AdminSection';
4
-
5
- @customElement('admin-about-me-section')
6
- export class AdminAboutMeSection extends AdminSection {
7
- private async handleUpload() {
8
- const input = this.shadowRoot?.querySelector('#aboutFile') as HTMLInputElement;
9
- if (input.files?.[0]) {
10
- try {
11
- await this.onUpload('about-me.md', input.files[0]);
12
- this.onStatusMessage('Upload successful!');
13
- } catch (e) {
14
- this.onStatusMessage('Upload failed.');
15
- }
16
- }
17
- }
18
-
19
- private async handleDelete() {
20
- if (!confirm('Delete about-me.md?')) return;
21
- try {
22
- await this.onDelete('about-me.md');
23
- } catch (e) {
24
- this.onStatusMessage('Delete failed.');
25
- }
26
- }
27
-
28
- render() {
29
- const aboutMe = this.getContent('about-me.md');
30
- return html`
31
- <div class="section">
32
- <h3>About Me Page <span class="required-badge">Required</span></h3>
33
- <p class="help-text">Content for your About Me page. Supports Markdown with frontmatter.</p>
34
-
35
- ${aboutMe ? html`
36
- <div class="current-file">
37
- <strong>Current:</strong> about-me.md (${aboutMe.size} bytes)
38
- <button class="btn-danger" @click=${this.handleDelete}>Delete</button>
39
- </div>
40
- ` : ''}
41
-
42
- <input type="file" id="aboutFile" accept=".md" />
43
- <button class="btn-primary" @click=${this.handleUpload}>Upload about-me.md</button>
44
- </div>
45
- `;
46
- }
47
- }
@@ -1,134 +0,0 @@
1
- import { LitElement, css } from 'lit';
2
- import { property } from 'lit/decorators.js';
3
- import type { ContentItem, StaticDetails, AdminSectionProps } from '../types';
4
-
5
- export abstract class AdminSection extends LitElement implements AdminSectionProps {
6
-
7
- static styles = css`
8
- :host {
9
- display: block;
10
- width: 100%;
11
- }
12
-
13
- .section {
14
- width: 100%;
15
- border: none;
16
- padding: 24px;
17
- border-radius: 16px;
18
- background: var(--card-bg, #fff);
19
- box-shadow: 0 4px 6px rgba(0,0,0,0.07), 0 10px 20px rgba(0,0,0,0.05);
20
- margin-bottom: 20px;
21
- min-height: 450px;
22
- box-sizing: border-box;
23
- }
24
-
25
- .section h3 {
26
- margin: 0 0 0.5rem 0;
27
- font-size: 1.25rem;
28
- }
29
-
30
- .help-text {
31
- color: var(--secondary-text, #666);
32
- margin-bottom: 1rem;
33
- }
34
-
35
- .current-file {
36
- margin-bottom: 1rem;
37
- padding: 1rem;
38
- background: var(--nav-link-hover-bg, #f5f5f5);
39
- border-radius: 8px;
40
- }
41
-
42
- input[type="text"],
43
- input[type="file"] {
44
- width: 100%;
45
- padding: 10px;
46
- margin-bottom: 1rem;
47
- border: 1px solid var(--border-color);
48
- border-radius: 4px;
49
- background: var(--background-color);
50
- color: var(--text-color);
51
- box-sizing: border-box;
52
- }
53
-
54
- input[type="file"] {
55
- margin-bottom: 1rem;
56
- }
57
-
58
- button {
59
- padding: 10px 20px;
60
- border: none;
61
- border-radius: 4px;
62
- cursor: pointer;
63
- font-size: 1rem;
64
- transition: background-color 0.3s ease;
65
- margin-right: 10px;
66
- }
67
-
68
- .btn-primary {
69
- background: var(--link-color, #646cff);
70
- color: white;
71
- }
72
-
73
- .btn-primary:hover {
74
- background: var(--link-hover-color, #535bf2);
75
- }
76
-
77
- .btn-secondary {
78
- background: var(--nav-link-hover-bg, #f0f0f0);
79
- color: var(--text-color, #213547);
80
- }
81
-
82
- .btn-secondary:hover {
83
- background: var(--border-color, #e0e0e0);
84
- }
85
-
86
- .btn-danger {
87
- background: #dc3545;
88
- color: white;
89
- }
90
-
91
- .btn-danger:hover {
92
- background: #c82333;
93
- }
94
-
95
- .file-list {
96
- margin-top: 1rem;
97
- }
98
-
99
- .file-item {
100
- display: flex;
101
- justify-content: space-between;
102
- align-items: center;
103
- padding: 0.5rem;
104
- border-bottom: 1px solid var(--border-color, #eee);
105
- }
106
-
107
- .file-item:last-child {
108
- border-bottom: none;
109
- }
110
- `;
111
-
112
- @property({ type: Array })
113
- accessor contentList: ContentItem[] = [];
114
-
115
- @property({ type: Object })
116
- accessor staticDetails: StaticDetails = {};
117
-
118
- @property({ type: Function })
119
- accessor onUpload: (key: string, file: File) => Promise<void> = async () => {};
120
-
121
- @property({ type: Function })
122
- accessor onDelete: (key: string) => Promise<void> = async () => {};
123
-
124
- @property({ type: Function })
125
- accessor onStatusMessage: (message: string) => void = () => {};
126
-
127
- protected getContent(key: string): ContentItem | undefined {
128
- return this.contentList.find(c => c.key === key);
129
- }
130
-
131
- protected getSectionFiles(prefix: string): ContentItem[] {
132
- return this.contentList.filter(c => c.key.startsWith(prefix));
133
- }
134
- }
@@ -1,62 +0,0 @@
1
- import { html } from 'lit';
2
- import { customElement } from 'lit/decorators.js';
3
- import { AdminSection } from './AdminSection';
4
-
5
- @customElement('admin-blogs-section')
6
- export class AdminBlogsSection extends AdminSection {
7
- private async handleUpload() {
8
- const metaInput = this.shadowRoot?.querySelector('#blogMetaFile') as HTMLInputElement;
9
- const contentInput = this.shadowRoot?.querySelector('#blogContentFile') as HTMLInputElement;
10
- const slugInput = this.shadowRoot?.querySelector('#blogSlug') as HTMLInputElement;
11
-
12
- if (metaInput.files?.[0] && contentInput.files?.[0] && slugInput.value) {
13
- try {
14
- await this.onUpload(`blogs/${slugInput.value}.json`, metaInput.files[0]);
15
- await this.onUpload(`blogs/${slugInput.value}.md`, contentInput.files[0]);
16
- this.onStatusMessage('Upload successful!');
17
- } catch (e) {
18
- this.onStatusMessage('Upload failed.');
19
- }
20
- }
21
- }
22
-
23
- private async handleDelete(slug: string) {
24
- if (!confirm(`Delete blog ${slug}?`)) return;
25
- try {
26
- await this.onDelete(`blogs/${slug}.json`);
27
- await this.onDelete(`blogs/${slug}.md`);
28
- } catch (e) {
29
- this.onStatusMessage('Delete failed.');
30
- }
31
- }
32
-
33
- render() {
34
- const blogs = this.getSectionFiles('blogs/').filter(b => b.key.endsWith('.json'));
35
- return html`
36
- <div class="section">
37
- <h3>Blog Posts</h3>
38
- <p class="help-text">Each blog needs 2 files: a JSON (metadata) and MD (content) file.</p>
39
-
40
- <h4>Upload New Blog</h4>
41
- <input type="file" id="blogMetaFile" accept=".json" />
42
- <input type="file" id="blogContentFile" accept=".md" />
43
- <input type="text" id="blogSlug" placeholder="Slug (e.g., my-new-post)" class="mt-1" />
44
- <button class="btn-primary" @click=${this.handleUpload}>Upload Blog (JSON + MD)</button>
45
-
46
- <div class="file-list">
47
- <h4>Current Blogs (${blogs.length})</h4>
48
- ${blogs.map(b => {
49
- const slug = b.key.replace('blogs/', '').replace('.json', '');
50
- return html`
51
- <div class="file-item">
52
- <span>${b.key.replace('.json', '')}</span>
53
- <button class="btn-danger" @click=${() => this.handleDelete(slug)}>Delete</button>
54
- </div>
55
- `;
56
- })}
57
- ${blogs.length === 0 ? html`<p>No blogs yet.</p>` : ''}
58
- </div>
59
- </div>
60
- `;
61
- }
62
- }
@@ -1,47 +0,0 @@
1
- import { html } from 'lit';
2
- import { customElement } from 'lit/decorators.js';
3
- import { AdminSection } from './AdminSection';
4
-
5
- @customElement('admin-home-section')
6
- export class AdminHomeSection extends AdminSection {
7
- private async handleUpload() {
8
- const input = this.shadowRoot?.querySelector('#homeFile') as HTMLInputElement;
9
- if (input.files?.[0]) {
10
- try {
11
- await this.onUpload('home.md', input.files[0]);
12
- this.onStatusMessage('Upload successful!');
13
- } catch (e) {
14
- this.onStatusMessage('Upload failed.');
15
- }
16
- }
17
- }
18
-
19
- private async handleDelete() {
20
- if (!confirm('Delete home.md?')) return;
21
- try {
22
- await this.onDelete('home.md');
23
- } catch (e) {
24
- this.onStatusMessage('Delete failed.');
25
- }
26
- }
27
-
28
- render() {
29
- const home = this.getContent('home.md');
30
- return html`
31
- <div class="section">
32
- <h3>Home Page</h3>
33
- <p class="help-text">Content for your home page. Upload home.md with your main content.</p>
34
-
35
- ${home ? html`
36
- <div class="current-file">
37
- <strong>Current:</strong> home.md (${home.size} bytes)
38
- <button class="btn-danger" @click=${this.handleDelete}>Delete</button>
39
- </div>
40
- ` : ''}
41
-
42
- <input type="file" id="homeFile" accept=".md" />
43
- <button class="btn-primary" @click=${this.handleUpload}>Upload home.md</button>
44
- </div>
45
- `;
46
- }
47
- }
@@ -1,54 +0,0 @@
1
- import { html } from 'lit';
2
- import { customElement } from 'lit/decorators.js';
3
- import { AdminSection } from './AdminSection';
4
-
5
- @customElement('admin-images-section')
6
- export class AdminImagesSection extends AdminSection {
7
- private async handleUpload() {
8
- const fileInput = this.shadowRoot?.querySelector('#imageFile') as HTMLInputElement;
9
- const pathInput = this.shadowRoot?.querySelector('#imagePath') as HTMLInputElement;
10
-
11
- if (fileInput.files?.[0] && pathInput.value) {
12
- try {
13
- await this.onUpload(`images/${pathInput.value}`, fileInput.files[0]);
14
- this.onStatusMessage('Upload successful!');
15
- } catch (e) {
16
- this.onStatusMessage('Upload failed.');
17
- }
18
- }
19
- }
20
-
21
- private async handleDelete(key: string) {
22
- if (!confirm(`Delete ${key}?`)) return;
23
- try {
24
- await this.onDelete(key);
25
- } catch (e) {
26
- this.onStatusMessage('Delete failed.');
27
- }
28
- }
29
-
30
- render() {
31
- const images = this.getSectionFiles('images/');
32
- return html`
33
- <div class="section">
34
- <h3>Images</h3>
35
- <p class="help-text">Upload images for use in your content. In markdown, reference images by filename only (e.g., use <code>my-photo.jpg</code> not <code>/images/my-photo.jpg</code>). The renderer automatically prepends <code>images/</code>.</p>
36
-
37
- <input type="file" id="imageFile" accept="image/*" />
38
- <input type="text" id="imagePath" placeholder="Image name (e.g., profile-photo.jpg)" class="mt-1" />
39
- <button class="btn-primary" @click=${this.handleUpload}>Upload to images/</button>
40
-
41
- <div class="file-list">
42
- <h4>Current Images (${images.length})</h4>
43
- ${images.map(img => html`
44
- <div class="file-item">
45
- <span>${img.key} (${img.size} bytes)</span>
46
- <button class="btn-danger" @click=${() => this.handleDelete(img.key)}>Delete</button>
47
- </div>
48
- `)}
49
- ${images.length === 0 ? html`<p>No images yet.</p>` : ''}
50
- </div>
51
- </div>
52
- `;
53
- }
54
- }
@@ -1,116 +0,0 @@
1
- import { LitElement, html, css } from 'lit';
2
- import { customElement, property } from 'lit/decorators.js';
3
-
4
- @customElement('admin-login-form')
5
- export class AdminLoginForm extends LitElement {
6
-
7
- static styles = css`
8
- :host {
9
- display: block;
10
- width: 100%;
11
- }
12
-
13
- .container {
14
- width: 100%;
15
- max-width: 900px;
16
- margin: 0 auto;
17
- padding: 1rem;
18
- }
19
-
20
- .login-box {
21
- border: 1px solid var(--border-color, #e0e0e0);
22
- padding: 3rem 2.5rem;
23
- border-radius: 16px;
24
- width: 100%;
25
- max-width: 420px;
26
- margin: 80px auto;
27
- text-align: center;
28
- background: var(--card-bg, #fff);
29
- box-shadow: 0 4px 6px rgba(0,0,0,0.07), 0 10px 20px rgba(0,0,0,0.05);
30
- }
31
-
32
- .login-box h2 {
33
- margin: 0 0 0.5rem 0;
34
- font-size: 1.75rem;
35
- font-weight: 600;
36
- color: var(--text-color, #213547);
37
- }
38
-
39
- .login-box p {
40
- color: var(--secondary-text, #666);
41
- margin-bottom: 1.5rem;
42
- }
43
-
44
- input {
45
- width: 100%;
46
- padding: 10px;
47
- margin-bottom: 20px;
48
- border: 1px solid var(--border-color);
49
- border-radius: 4px;
50
- background: var(--background-color);
51
- color: var(--text-color);
52
- box-sizing: border-box;
53
- }
54
-
55
- button {
56
- padding: 10px 20px;
57
- border: none;
58
- border-radius: 4px;
59
- cursor: pointer;
60
- font-size: 1rem;
61
- transition: background-color 0.3s ease;
62
- }
63
-
64
- .btn-primary {
65
- background: var(--link-color, #646cff);
66
- color: white;
67
- }
68
-
69
- .btn-primary:hover {
70
- background: var(--link-hover-color, #535bf2);
71
- }
72
-
73
- .error-message {
74
- color: red;
75
- margin-bottom: 10px;
76
- }
77
- `;
78
-
79
- @property({ type: String })
80
- accessor errorMessage = '';
81
-
82
- @property({ type: Boolean })
83
- accessor isSetup = false;
84
-
85
- private handleSubmit(e: Event) {
86
- e.preventDefault();
87
- const formData = new FormData(e.target as HTMLFormElement);
88
- const username = formData.get('username') as string;
89
- const password = formData.get('password') as string;
90
- const confirmPassword = formData.get('confirmPassword') as string;
91
-
92
- this.dispatchEvent(new CustomEvent('login-submit', {
93
- detail: { username, password, confirmPassword },
94
- bubbles: true,
95
- composed: true
96
- }));
97
- }
98
-
99
- render() {
100
- return html`
101
- <div class="container">
102
- <div class="login-box">
103
- <h2>${this.isSetup ? 'Admin Login' : 'Admin Setup'}</h2>
104
- <p>${this.isSetup ? 'Enter your credentials' : 'Create your admin credentials'}</p>
105
- <form @submit=${this.handleSubmit}>
106
- <input type="text" name="username" placeholder="Username${this.isSetup ? '' : ' (3+ chars)'}" autocomplete="username" required />
107
- <input type="password" name="password" placeholder="Password${this.isSetup ? '' : ' (8+ chars)'}" autocomplete="current-password" required />
108
- ${!this.isSetup ? html`<input type="password" name="confirmPassword" placeholder="Confirm Password" required />` : ''}
109
- ${this.errorMessage ? html`<div class="error-message">${this.errorMessage}</div>` : ''}
110
- <button type="submit" class="btn-primary">${this.isSetup ? 'Login' : 'Create Account'}</button>
111
- </form>
112
- </div>
113
- </div>
114
- `;
115
- }
116
- }
@@ -1,51 +0,0 @@
1
- import { html } from 'lit';
2
- import { customElement } from 'lit/decorators.js';
3
- import { AdminSection } from './AdminSection';
4
-
5
- @customElement('admin-logo-section')
6
- export class AdminLogoSection extends AdminSection {
7
- private async handleUpload() {
8
- const input = this.shadowRoot?.querySelector('#logoFile') as HTMLInputElement;
9
- if (input.files?.[0]) {
10
- try {
11
- await this.onUpload('logo.svg', input.files[0]);
12
- this.onStatusMessage('Upload successful!');
13
- } catch (e) {
14
- this.onStatusMessage('Upload failed.');
15
- }
16
- }
17
- }
18
-
19
- private async handleDelete() {
20
- if (!confirm('Delete logo.svg?')) return;
21
- try {
22
- await this.onDelete('logo.svg');
23
- } catch (e) {
24
- this.onStatusMessage('Delete failed.');
25
- }
26
- }
27
-
28
- render() {
29
- const logo = this.getContent('logo.svg');
30
- return html`
31
- <div class="section">
32
- <h3>Site Logo</h3>
33
- <p class="help-text">Upload your site logo (SVG format recommended). This appears in the header of your site.</p>
34
-
35
- ${logo ? html`
36
- <div class="current-file">
37
- <strong>Current:</strong> logo.svg (${logo.size} bytes)
38
- <button class="btn-danger" @click=${this.handleDelete}>Delete</button>
39
- </div>
40
- ` : ''}
41
-
42
- <input type="file" id="logoFile" accept=".svg,image/svg+xml" />
43
- <button class="btn-primary mt-1 mb-1" @click=${this.handleUpload}>Upload logo.svg</button>
44
-
45
- <div class="info-box">
46
- <strong>Tip:</strong> Use an SVG with transparent background. The logo will automatically adapt to light/dark themes.
47
- </div>
48
- </div>
49
- `;
50
- }
51
- }