@htlkg/astro 0.0.1
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.
- package/README.md +265 -0
- package/dist/chunk-33R4URZV.js +59 -0
- package/dist/chunk-33R4URZV.js.map +1 -0
- package/dist/chunk-64USRLVP.js +85 -0
- package/dist/chunk-64USRLVP.js.map +1 -0
- package/dist/chunk-WLOFOVCL.js +210 -0
- package/dist/chunk-WLOFOVCL.js.map +1 -0
- package/dist/chunk-WNMPTDCR.js +73 -0
- package/dist/chunk-WNMPTDCR.js.map +1 -0
- package/dist/chunk-Z2ZAL7KX.js +9 -0
- package/dist/chunk-Z2ZAL7KX.js.map +1 -0
- package/dist/chunk-ZQ4XMJH7.js +1 -0
- package/dist/chunk-ZQ4XMJH7.js.map +1 -0
- package/dist/htlkg/config.js +7 -0
- package/dist/htlkg/config.js.map +1 -0
- package/dist/htlkg/index.js +7 -0
- package/dist/htlkg/index.js.map +1 -0
- package/dist/index.js +64 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/index.js +168 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/utils/hydration.js +21 -0
- package/dist/utils/hydration.js.map +1 -0
- package/dist/utils/index.js +56 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/ssr.js +21 -0
- package/dist/utils/ssr.js.map +1 -0
- package/dist/utils/static.js +19 -0
- package/dist/utils/static.js.map +1 -0
- package/package.json +53 -0
- package/src/__mocks__/astro-middleware.ts +19 -0
- package/src/__mocks__/virtual-htlkg-config.ts +14 -0
- package/src/auth/LoginForm.vue +482 -0
- package/src/auth/LoginPage.astro +70 -0
- package/src/components/PageHeader.astro +145 -0
- package/src/components/Sidebar.astro +157 -0
- package/src/components/Topbar.astro +167 -0
- package/src/components/index.ts +9 -0
- package/src/htlkg/config.test.ts +165 -0
- package/src/htlkg/config.ts +242 -0
- package/src/htlkg/index.ts +245 -0
- package/src/htlkg/virtual-modules.test.ts +158 -0
- package/src/htlkg/virtual-modules.ts +81 -0
- package/src/index.ts +37 -0
- package/src/layouts/AdminLayout.astro +184 -0
- package/src/layouts/AuthLayout.astro +164 -0
- package/src/layouts/BrandLayout.astro +309 -0
- package/src/layouts/DefaultLayout.astro +25 -0
- package/src/layouts/PublicLayout.astro +153 -0
- package/src/layouts/index.ts +10 -0
- package/src/middleware/auth.ts +53 -0
- package/src/middleware/index.ts +31 -0
- package/src/middleware/route-guards.test.ts +182 -0
- package/src/middleware/route-guards.ts +218 -0
- package/src/patterns/admin/DetailPage.astro +195 -0
- package/src/patterns/admin/FormPage.astro +203 -0
- package/src/patterns/admin/ListPage.astro +178 -0
- package/src/patterns/admin/index.ts +9 -0
- package/src/patterns/brand/ConfigPage.astro +128 -0
- package/src/patterns/brand/PortalPage.astro +161 -0
- package/src/patterns/brand/index.ts +8 -0
- package/src/patterns/index.ts +8 -0
- package/src/utils/hydration.test.ts +154 -0
- package/src/utils/hydration.ts +151 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/ssr.test.ts +235 -0
- package/src/utils/ssr.ts +139 -0
- package/src/utils/static.test.ts +144 -0
- package/src/utils/static.ts +144 -0
- package/src/vue-app-setup.ts +88 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Admin Form Page Pattern
|
|
4
|
+
*
|
|
5
|
+
* Standard pattern for admin form pages with sections and actions.
|
|
6
|
+
* Provides consistent structure for create/edit pages.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```astro
|
|
10
|
+
* <FormPage
|
|
11
|
+
* title="Create User"
|
|
12
|
+
* description="Add a new user to the system"
|
|
13
|
+
* user={user}
|
|
14
|
+
* >
|
|
15
|
+
* <div slot="actions">
|
|
16
|
+
* <button>Cancel</button>
|
|
17
|
+
* <button>Save</button>
|
|
18
|
+
* </div>
|
|
19
|
+
* <div slot="content">
|
|
20
|
+
* <UserForm />
|
|
21
|
+
* </div>
|
|
22
|
+
* </FormPage>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import AdminLayout from "../../layouts/AdminLayout.astro";
|
|
27
|
+
import PageHeader from "../../components/PageHeader.astro";
|
|
28
|
+
import type { AuthUser } from "@htlkg/core/types";
|
|
29
|
+
|
|
30
|
+
interface BreadcrumbItem {
|
|
31
|
+
label: string;
|
|
32
|
+
href?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface Props {
|
|
36
|
+
title: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
user: AuthUser;
|
|
39
|
+
currentPage?: string;
|
|
40
|
+
breadcrumbs?: BreadcrumbItem[];
|
|
41
|
+
showSidebar?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const {
|
|
45
|
+
title,
|
|
46
|
+
description,
|
|
47
|
+
user,
|
|
48
|
+
currentPage,
|
|
49
|
+
breadcrumbs = [],
|
|
50
|
+
showSidebar = false,
|
|
51
|
+
} = Astro.props;
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
<AdminLayout
|
|
55
|
+
title={title}
|
|
56
|
+
description={description}
|
|
57
|
+
user={user}
|
|
58
|
+
currentPage={currentPage}
|
|
59
|
+
>
|
|
60
|
+
<div slot="actions">
|
|
61
|
+
<slot name="actions" />
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div class:list={["form-page", { "with-sidebar": showSidebar }]}>
|
|
65
|
+
<PageHeader
|
|
66
|
+
title={title}
|
|
67
|
+
description={description}
|
|
68
|
+
breadcrumbs={breadcrumbs}
|
|
69
|
+
/>
|
|
70
|
+
|
|
71
|
+
<div class="form-container">
|
|
72
|
+
<div class="form-content">
|
|
73
|
+
<slot name="content" />
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
{
|
|
77
|
+
showSidebar && (
|
|
78
|
+
<aside class="form-sidebar">
|
|
79
|
+
<slot name="sidebar" />
|
|
80
|
+
</aside>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="form-footer">
|
|
86
|
+
<slot name="footer">
|
|
87
|
+
<div class="footer-actions">
|
|
88
|
+
<button type="button" class="btn-secondary"> Cancel </button>
|
|
89
|
+
<button type="submit" class="btn-primary"> Save </button>
|
|
90
|
+
</div>
|
|
91
|
+
</slot>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</AdminLayout>
|
|
95
|
+
|
|
96
|
+
<style>
|
|
97
|
+
.form-page {
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-direction: column;
|
|
100
|
+
gap: 1.5rem;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.form-container {
|
|
104
|
+
display: grid;
|
|
105
|
+
grid-template-columns: 1fr;
|
|
106
|
+
gap: 1.5rem;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.form-page.with-sidebar .form-container {
|
|
110
|
+
grid-template-columns: 1fr 20rem;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.form-content {
|
|
114
|
+
background: white;
|
|
115
|
+
border-radius: 0.5rem;
|
|
116
|
+
border: 1px solid #e5e7eb;
|
|
117
|
+
padding: 1.5rem;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.form-sidebar {
|
|
121
|
+
background: white;
|
|
122
|
+
border-radius: 0.5rem;
|
|
123
|
+
border: 1px solid #e5e7eb;
|
|
124
|
+
padding: 1.5rem;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.form-footer {
|
|
128
|
+
background: white;
|
|
129
|
+
border-radius: 0.5rem;
|
|
130
|
+
border: 1px solid #e5e7eb;
|
|
131
|
+
padding: 1.5rem;
|
|
132
|
+
display: flex;
|
|
133
|
+
justify-content: flex-end;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.footer-actions {
|
|
137
|
+
display: flex;
|
|
138
|
+
gap: 0.75rem;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.btn-secondary {
|
|
142
|
+
padding: 0.5rem 1rem;
|
|
143
|
+
border: 1px solid #d1d5db;
|
|
144
|
+
border-radius: 0.5rem;
|
|
145
|
+
background: white;
|
|
146
|
+
color: #374151;
|
|
147
|
+
font-size: 0.875rem;
|
|
148
|
+
font-weight: 500;
|
|
149
|
+
cursor: pointer;
|
|
150
|
+
transition: all 0.2s;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.btn-secondary:hover {
|
|
154
|
+
background: #f9fafb;
|
|
155
|
+
border-color: #9ca3af;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.btn-primary {
|
|
159
|
+
padding: 0.5rem 1rem;
|
|
160
|
+
border: none;
|
|
161
|
+
border-radius: 0.5rem;
|
|
162
|
+
background: #2563eb;
|
|
163
|
+
color: white;
|
|
164
|
+
font-size: 0.875rem;
|
|
165
|
+
font-weight: 500;
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
transition: all 0.2s;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.btn-primary:hover {
|
|
171
|
+
background: #1d4ed8;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.btn-primary:disabled {
|
|
175
|
+
background: #9ca3af;
|
|
176
|
+
cursor: not-allowed;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* Responsive */
|
|
180
|
+
@media (max-width: 1024px) {
|
|
181
|
+
.form-page.with-sidebar .form-container {
|
|
182
|
+
grid-template-columns: 1fr;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@media (max-width: 768px) {
|
|
187
|
+
.form-content,
|
|
188
|
+
.form-sidebar,
|
|
189
|
+
.form-footer {
|
|
190
|
+
padding: 1rem;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.footer-actions {
|
|
194
|
+
width: 100%;
|
|
195
|
+
flex-direction: column-reverse;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.btn-secondary,
|
|
199
|
+
.btn-primary {
|
|
200
|
+
width: 100%;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
</style>
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Admin List Page Pattern
|
|
4
|
+
*
|
|
5
|
+
* Standard pattern for admin list pages with filters, search, and table.
|
|
6
|
+
* Provides consistent structure for entity listing pages.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```astro
|
|
10
|
+
* <ListPage
|
|
11
|
+
* title="Users"
|
|
12
|
+
* description="Manage system users"
|
|
13
|
+
* user={user}
|
|
14
|
+
* >
|
|
15
|
+
* <div slot="filters">
|
|
16
|
+
* <select>...</select>
|
|
17
|
+
* </div>
|
|
18
|
+
* <div slot="actions">
|
|
19
|
+
* <button>Add User</button>
|
|
20
|
+
* </div>
|
|
21
|
+
* <Table slot="content" items={users} />
|
|
22
|
+
* </ListPage>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import AdminLayout from "../../layouts/AdminLayout.astro";
|
|
27
|
+
import PageHeader from "../../components/PageHeader.astro";
|
|
28
|
+
import type { AuthUser } from "@htlkg/core/types";
|
|
29
|
+
|
|
30
|
+
interface BreadcrumbItem {
|
|
31
|
+
label: string;
|
|
32
|
+
href?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface Props {
|
|
36
|
+
title: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
user: AuthUser;
|
|
39
|
+
currentPage?: string;
|
|
40
|
+
breadcrumbs?: BreadcrumbItem[];
|
|
41
|
+
showFilters?: boolean;
|
|
42
|
+
showSearch?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const {
|
|
46
|
+
title,
|
|
47
|
+
description,
|
|
48
|
+
user,
|
|
49
|
+
currentPage,
|
|
50
|
+
breadcrumbs = [],
|
|
51
|
+
showFilters = true,
|
|
52
|
+
showSearch = true,
|
|
53
|
+
} = Astro.props;
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
<AdminLayout
|
|
57
|
+
title={title}
|
|
58
|
+
description={description}
|
|
59
|
+
user={user}
|
|
60
|
+
currentPage={currentPage}
|
|
61
|
+
>
|
|
62
|
+
<div slot="actions">
|
|
63
|
+
<slot name="actions" />
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="list-page">
|
|
67
|
+
<PageHeader
|
|
68
|
+
title={title}
|
|
69
|
+
description={description}
|
|
70
|
+
breadcrumbs={breadcrumbs}
|
|
71
|
+
/>
|
|
72
|
+
|
|
73
|
+
{
|
|
74
|
+
(showFilters || showSearch) && (
|
|
75
|
+
<div class="list-toolbar">
|
|
76
|
+
{showSearch && (
|
|
77
|
+
<div class="search-box">
|
|
78
|
+
<slot name="search">
|
|
79
|
+
<input
|
|
80
|
+
type="search"
|
|
81
|
+
placeholder="Search..."
|
|
82
|
+
class="search-input"
|
|
83
|
+
/>
|
|
84
|
+
</slot>
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
{showFilters && (
|
|
89
|
+
<div class="filters">
|
|
90
|
+
<slot name="filters" />
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
<div class="list-content">
|
|
98
|
+
<slot name="content" />
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div class="list-footer">
|
|
102
|
+
<slot name="footer" />
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</AdminLayout>
|
|
106
|
+
|
|
107
|
+
<style>
|
|
108
|
+
.list-page {
|
|
109
|
+
display: flex;
|
|
110
|
+
flex-direction: column;
|
|
111
|
+
gap: 1.5rem;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.list-toolbar {
|
|
115
|
+
display: flex;
|
|
116
|
+
gap: 1rem;
|
|
117
|
+
align-items: center;
|
|
118
|
+
flex-wrap: wrap;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.search-box {
|
|
122
|
+
flex: 1;
|
|
123
|
+
min-width: 16rem;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.search-input {
|
|
127
|
+
width: 100%;
|
|
128
|
+
padding: 0.5rem 1rem;
|
|
129
|
+
border: 1px solid #d1d5db;
|
|
130
|
+
border-radius: 0.5rem;
|
|
131
|
+
font-size: 0.875rem;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.search-input:focus {
|
|
135
|
+
outline: none;
|
|
136
|
+
border-color: #2563eb;
|
|
137
|
+
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.filters {
|
|
141
|
+
display: flex;
|
|
142
|
+
gap: 0.75rem;
|
|
143
|
+
flex-wrap: wrap;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.list-content {
|
|
147
|
+
background: white;
|
|
148
|
+
border-radius: 0.5rem;
|
|
149
|
+
border: 1px solid #e5e7eb;
|
|
150
|
+
overflow: hidden;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.list-footer {
|
|
154
|
+
display: flex;
|
|
155
|
+
justify-content: space-between;
|
|
156
|
+
align-items: center;
|
|
157
|
+
padding: 1rem;
|
|
158
|
+
background: white;
|
|
159
|
+
border-radius: 0.5rem;
|
|
160
|
+
border: 1px solid #e5e7eb;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* Responsive */
|
|
164
|
+
@media (max-width: 768px) {
|
|
165
|
+
.list-toolbar {
|
|
166
|
+
flex-direction: column;
|
|
167
|
+
align-items: stretch;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.search-box {
|
|
171
|
+
width: 100%;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.filters {
|
|
175
|
+
width: 100%;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @htlkg/pages - Admin Page Patterns
|
|
3
|
+
*
|
|
4
|
+
* Reusable page patterns for admin interfaces.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { default as ListPage } from './ListPage.astro';
|
|
8
|
+
export { default as DetailPage } from './DetailPage.astro';
|
|
9
|
+
export { default as FormPage } from './FormPage.astro';
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Brand Config Page Pattern
|
|
4
|
+
*
|
|
5
|
+
* Standard pattern for brand configuration pages.
|
|
6
|
+
* Used for brand-specific admin pages with brand context.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```astro
|
|
10
|
+
* <ConfigPage
|
|
11
|
+
* title="WiFi Settings"
|
|
12
|
+
* brandId={brandId}
|
|
13
|
+
* brand={brand}
|
|
14
|
+
* user={user}
|
|
15
|
+
* >
|
|
16
|
+
* <div slot="actions">
|
|
17
|
+
* <button>Save</button>
|
|
18
|
+
* </div>
|
|
19
|
+
* <WiFiConfigForm />
|
|
20
|
+
* </ConfigPage>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import BrandLayout from "../../layouts/BrandLayout.astro";
|
|
25
|
+
import PageHeader from "../../components/PageHeader.astro";
|
|
26
|
+
import type { AuthUser, Brand } from "@htlkg/core/types";
|
|
27
|
+
|
|
28
|
+
interface NavItem {
|
|
29
|
+
label: string;
|
|
30
|
+
href: string;
|
|
31
|
+
active?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface Props {
|
|
35
|
+
title: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
brandId: number;
|
|
38
|
+
brand: Brand;
|
|
39
|
+
user: AuthUser;
|
|
40
|
+
navItems?: NavItem[];
|
|
41
|
+
showSidebar?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const {
|
|
45
|
+
title,
|
|
46
|
+
description,
|
|
47
|
+
brandId,
|
|
48
|
+
brand,
|
|
49
|
+
user,
|
|
50
|
+
navItems = [],
|
|
51
|
+
showSidebar = false,
|
|
52
|
+
} = Astro.props;
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
<BrandLayout
|
|
56
|
+
title={title}
|
|
57
|
+
description={description}
|
|
58
|
+
brandId={brandId}
|
|
59
|
+
brand={brand}
|
|
60
|
+
user={user}
|
|
61
|
+
navItems={navItems}
|
|
62
|
+
>
|
|
63
|
+
<div slot="actions">
|
|
64
|
+
<slot name="actions" />
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div class:list={["config-page", { "with-sidebar": showSidebar }]}>
|
|
68
|
+
<div class="config-container">
|
|
69
|
+
<div class="config-content">
|
|
70
|
+
<slot />
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{
|
|
74
|
+
showSidebar && (
|
|
75
|
+
<aside class="config-sidebar">
|
|
76
|
+
<slot name="sidebar" />
|
|
77
|
+
</aside>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</BrandLayout>
|
|
83
|
+
|
|
84
|
+
<style>
|
|
85
|
+
.config-page {
|
|
86
|
+
display: flex;
|
|
87
|
+
flex-direction: column;
|
|
88
|
+
gap: 1.5rem;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.config-container {
|
|
92
|
+
display: grid;
|
|
93
|
+
grid-template-columns: 1fr;
|
|
94
|
+
gap: 1.5rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.config-page.with-sidebar .config-container {
|
|
98
|
+
grid-template-columns: 1fr 20rem;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.config-content {
|
|
102
|
+
background: white;
|
|
103
|
+
border-radius: 0.5rem;
|
|
104
|
+
border: 1px solid #e5e7eb;
|
|
105
|
+
padding: 1.5rem;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.config-sidebar {
|
|
109
|
+
background: white;
|
|
110
|
+
border-radius: 0.5rem;
|
|
111
|
+
border: 1px solid #e5e7eb;
|
|
112
|
+
padding: 1.5rem;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Responsive */
|
|
116
|
+
@media (max-width: 1024px) {
|
|
117
|
+
.config-page.with-sidebar .config-container {
|
|
118
|
+
grid-template-columns: 1fr;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@media (max-width: 768px) {
|
|
123
|
+
.config-content,
|
|
124
|
+
.config-sidebar {
|
|
125
|
+
padding: 1rem;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
</style>
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Brand Portal Page Pattern
|
|
4
|
+
*
|
|
5
|
+
* Standard pattern for brand portal pages with brand context.
|
|
6
|
+
* Used for brand-specific public-facing pages like WiFi portals.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```astro
|
|
10
|
+
* <PortalPage
|
|
11
|
+
* title="WiFi Login"
|
|
12
|
+
* brandId={brandId}
|
|
13
|
+
* brand={brand}
|
|
14
|
+
* >
|
|
15
|
+
* <WiFiLoginForm />
|
|
16
|
+
* </PortalPage>
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { Brand } from "@htlkg/core/types";
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
title: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
brandId: number;
|
|
26
|
+
brand: Brand;
|
|
27
|
+
showBranding?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { title, description, brandId, brand, showBranding = true } = Astro.props;
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
<!doctype html>
|
|
34
|
+
<html lang="en">
|
|
35
|
+
<head>
|
|
36
|
+
<meta charset="UTF-8" />
|
|
37
|
+
<link
|
|
38
|
+
rel="icon"
|
|
39
|
+
href={brand.logo || "https://images.hotelinking.com/login/favicon.ico"}
|
|
40
|
+
/>
|
|
41
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
42
|
+
<title>{title} | {brand.name}</title>
|
|
43
|
+
{description && <meta name="description" content={description} />}
|
|
44
|
+
</head>
|
|
45
|
+
<body>
|
|
46
|
+
<div id="app" class="portal-page">
|
|
47
|
+
<div class="portal-container">
|
|
48
|
+
{
|
|
49
|
+
showBranding && (
|
|
50
|
+
<div class="portal-header">
|
|
51
|
+
{brand.logo && (
|
|
52
|
+
<img src={brand.logo} alt={brand.name} class="brand-logo" />
|
|
53
|
+
)}
|
|
54
|
+
<h1 class="brand-name">{brand.name}</h1>
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
<div class="portal-content">
|
|
60
|
+
<slot />
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="portal-footer">
|
|
64
|
+
<slot name="footer">
|
|
65
|
+
<p class="footer-text">
|
|
66
|
+
Powered by <strong>Hotelinking</strong>
|
|
67
|
+
</p>
|
|
68
|
+
</slot>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</body>
|
|
73
|
+
</html>
|
|
74
|
+
|
|
75
|
+
<style>
|
|
76
|
+
.portal-page {
|
|
77
|
+
min-height: 100vh;
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
justify-content: center;
|
|
81
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
82
|
+
padding: 2rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.portal-container {
|
|
86
|
+
width: 100%;
|
|
87
|
+
max-width: 28rem;
|
|
88
|
+
background: white;
|
|
89
|
+
border-radius: 1rem;
|
|
90
|
+
box-shadow:
|
|
91
|
+
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
|
92
|
+
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
93
|
+
overflow: hidden;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.portal-header {
|
|
97
|
+
padding: 2rem 2rem 1rem;
|
|
98
|
+
text-align: center;
|
|
99
|
+
border-bottom: 1px solid #e5e7eb;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.brand-logo {
|
|
103
|
+
width: 5rem;
|
|
104
|
+
height: 5rem;
|
|
105
|
+
border-radius: 0.75rem;
|
|
106
|
+
object-fit: cover;
|
|
107
|
+
margin: 0 auto 1rem;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.brand-name {
|
|
111
|
+
font-size: 1.5rem;
|
|
112
|
+
font-weight: 700;
|
|
113
|
+
color: #111827;
|
|
114
|
+
margin: 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.portal-content {
|
|
118
|
+
padding: 2rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.portal-footer {
|
|
122
|
+
padding: 1.5rem 2rem;
|
|
123
|
+
background: #f9fafb;
|
|
124
|
+
border-top: 1px solid #e5e7eb;
|
|
125
|
+
text-align: center;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.footer-text {
|
|
129
|
+
font-size: 0.875rem;
|
|
130
|
+
color: #6b7280;
|
|
131
|
+
margin: 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Responsive */
|
|
135
|
+
@media (max-width: 640px) {
|
|
136
|
+
.portal-page {
|
|
137
|
+
padding: 1rem;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.portal-header {
|
|
141
|
+
padding: 1.5rem 1.5rem 0.75rem;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.portal-content {
|
|
145
|
+
padding: 1.5rem;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.portal-footer {
|
|
149
|
+
padding: 1rem 1.5rem;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.brand-logo {
|
|
153
|
+
width: 4rem;
|
|
154
|
+
height: 4rem;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.brand-name {
|
|
158
|
+
font-size: 1.25rem;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
</style>
|