@htlkg/astro 0.0.1 → 0.0.3
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 +24 -8
- package/dist/chunk-2GML443T.js +273 -0
- package/dist/chunk-2GML443T.js.map +1 -0
- package/dist/{chunk-Z2ZAL7KX.js → chunk-UBF5F2RG.js} +1 -1
- package/dist/{chunk-Z2ZAL7KX.js.map → chunk-UBF5F2RG.js.map} +1 -1
- package/dist/chunk-XOY5BM3N.js +151 -0
- package/dist/chunk-XOY5BM3N.js.map +1 -0
- package/dist/htlkg/config.js +1 -1
- package/dist/htlkg/index.js +1 -1
- package/dist/index.js +126 -14
- package/dist/index.js.map +1 -1
- package/dist/middleware/index.js +27 -28
- package/dist/middleware/index.js.map +1 -1
- package/dist/utils/index.js +31 -12
- package/dist/vue-app-setup.js +47 -0
- package/dist/vue-app-setup.js.map +1 -0
- package/package.json +60 -26
- package/src/auth/auth.md +77 -0
- package/src/components/Island.astro +56 -0
- package/src/components/components.md +79 -0
- package/src/factories/createListPage.ts +290 -0
- package/src/factories/index.ts +16 -0
- package/src/htlkg/config.ts +10 -0
- package/src/htlkg/htlkg.md +63 -0
- package/src/htlkg/index.ts +49 -157
- package/src/index.ts +3 -0
- package/src/layouts/AdminLayout.astro +103 -92
- package/src/layouts/layouts.md +87 -0
- package/src/middleware/auth.ts +42 -0
- package/src/middleware/middleware.md +82 -0
- package/src/middleware/route-guards.ts +4 -28
- package/src/patterns/patterns.md +104 -0
- package/src/utils/filters.ts +320 -0
- package/src/utils/index.ts +8 -2
- package/src/utils/params.ts +260 -0
- package/src/utils/utils.md +86 -0
- package/src/vue-app-setup.ts +21 -28
- package/dist/chunk-WLOFOVCL.js +0 -210
- package/dist/chunk-WLOFOVCL.js.map +0 -1
- package/dist/chunk-ZQ4XMJH7.js +0 -1
- package/dist/chunk-ZQ4XMJH7.js.map +0 -1
package/src/htlkg/index.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* htlkg Astro Integration
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* - Tailwind CSS integration
|
|
6
|
-
* - Vue 3 integration with Amplify setup
|
|
7
|
-
* - Authentication middleware
|
|
8
|
-
* - Route guards
|
|
9
|
-
* - Login page generation
|
|
4
|
+
* Supports static, hybrid, and full SSR output modes.
|
|
10
5
|
*/
|
|
11
6
|
|
|
12
7
|
import tailwind from '@astrojs/tailwind';
|
|
@@ -14,47 +9,13 @@ import vue from '@astrojs/vue';
|
|
|
14
9
|
import type { AstroIntegration } from 'astro';
|
|
15
10
|
import type { HtlkgIntegrationOptions } from './config.js';
|
|
16
11
|
import { createVirtualModulePlugin, virtualModuleTypes } from './virtual-modules.js';
|
|
12
|
+
import vueDevTools from 'vite-plugin-vue-devtools';
|
|
17
13
|
|
|
18
|
-
/**
|
|
19
|
-
* Default environment variables required for AWS Amplify authentication
|
|
20
|
-
*/
|
|
21
14
|
const DEFAULT_ENV_VARS = [
|
|
22
15
|
'PUBLIC_COGNITO_USER_POOL_ID',
|
|
23
16
|
'PUBLIC_COGNITO_USER_POOL_CLIENT_ID'
|
|
24
17
|
];
|
|
25
18
|
|
|
26
|
-
/**
|
|
27
|
-
* htlkg Astro integration that provides zero-config authentication setup.
|
|
28
|
-
*
|
|
29
|
-
* This integration automatically:
|
|
30
|
-
* - Includes Tailwind CSS integration (can be disabled)
|
|
31
|
-
* - Includes Vue 3 integration with Amplify and Nanostores setup
|
|
32
|
-
* - Injects authentication middleware for AWS Amplify
|
|
33
|
-
* - Configures route guards based on declarative configuration
|
|
34
|
-
* - Validates required environment variables (optional)
|
|
35
|
-
* - Injects TypeScript types for Astro.locals.user
|
|
36
|
-
* - Provides a default login page (optional)
|
|
37
|
-
*
|
|
38
|
-
* @param options - Configuration options for the integration
|
|
39
|
-
* @returns Astro integration object or array of integrations
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* // astro.config.mjs
|
|
43
|
-
* import { htlkg } from '@htlkg/astro';
|
|
44
|
-
*
|
|
45
|
-
* export default defineConfig({
|
|
46
|
-
* integrations: [
|
|
47
|
-
* htlkg({
|
|
48
|
-
* tailwind: { configFile: './tailwind.config.mjs' },
|
|
49
|
-
* auth: {
|
|
50
|
-
* publicRoutes: ['/login', '/'],
|
|
51
|
-
* adminRoutes: [/^\/admin/],
|
|
52
|
-
* loginUrl: '/login'
|
|
53
|
-
* }
|
|
54
|
-
* })
|
|
55
|
-
* ]
|
|
56
|
-
* });
|
|
57
|
-
*/
|
|
58
19
|
export function htlkg(
|
|
59
20
|
options: HtlkgIntegrationOptions = {},
|
|
60
21
|
): AstroIntegration | AstroIntegration[] {
|
|
@@ -65,6 +26,7 @@ export function htlkg(
|
|
|
65
26
|
requiredEnvVars = DEFAULT_ENV_VARS,
|
|
66
27
|
tailwind: tailwindOptions,
|
|
67
28
|
amplify,
|
|
29
|
+
vueAppSetup = 'auto',
|
|
68
30
|
} = options;
|
|
69
31
|
|
|
70
32
|
const integrations: AstroIntegration[] = [];
|
|
@@ -78,146 +40,77 @@ export function htlkg(
|
|
|
78
40
|
);
|
|
79
41
|
}
|
|
80
42
|
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
43
|
+
// Determine Vue setup mode:
|
|
44
|
+
// - 'full': Use Amplify app entrypoint (requires SSR)
|
|
45
|
+
// - 'basic': Basic Vue without app entrypoint (works with static)
|
|
46
|
+
// - 'auto': Default to 'basic' for compatibility with static builds
|
|
47
|
+
const useFullVueSetup = vueAppSetup === 'full';
|
|
48
|
+
|
|
49
|
+
// Add Vue integration
|
|
50
|
+
if (useFullVueSetup) {
|
|
51
|
+
integrations.push(vue({ appEntrypoint: '@htlkg/astro/vue-app-setup' }));
|
|
52
|
+
} else {
|
|
53
|
+
integrations.push(vue());
|
|
54
|
+
}
|
|
88
55
|
|
|
89
56
|
// Add the main htlkg integration
|
|
90
57
|
integrations.push({
|
|
91
58
|
name: '@htlkg/astro',
|
|
92
59
|
hooks: {
|
|
93
|
-
'astro:config:setup': ({
|
|
94
|
-
config,
|
|
95
|
-
updateConfig,
|
|
96
|
-
addMiddleware,
|
|
97
|
-
injectRoute,
|
|
98
|
-
logger,
|
|
99
|
-
}) => {
|
|
60
|
+
'astro:config:setup': ({ updateConfig, addMiddleware, injectRoute, logger }) => {
|
|
100
61
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
);
|
|
105
|
-
if (hasVue) {
|
|
106
|
-
logger.info('Vue integration configured with Amplify app setup');
|
|
107
|
-
}
|
|
62
|
+
logger.info(useFullVueSetup
|
|
63
|
+
? 'Vue configured with Amplify app setup'
|
|
64
|
+
: 'Vue configured (basic mode)');
|
|
108
65
|
|
|
109
|
-
// 2. Amplify will be configured by the middleware on first request
|
|
110
66
|
if (amplify) {
|
|
111
|
-
logger.info('Amplify configuration provided
|
|
112
|
-
} else {
|
|
113
|
-
logger.info('No Amplify configuration provided - will use environment variables');
|
|
67
|
+
logger.info('Amplify configuration provided');
|
|
114
68
|
}
|
|
115
69
|
|
|
116
|
-
//
|
|
117
|
-
if (validateEnv && !amplify) {
|
|
118
|
-
const missing = requiredEnvVars.filter(
|
|
119
|
-
(varName) => !process.env[varName],
|
|
120
|
-
);
|
|
121
|
-
|
|
70
|
+
// Validate env vars (only for full setup)
|
|
71
|
+
if (validateEnv && !amplify && useFullVueSetup) {
|
|
72
|
+
const missing = requiredEnvVars.filter(v => !process.env[v]);
|
|
122
73
|
if (missing.length > 0) {
|
|
123
|
-
logger.warn(
|
|
124
|
-
`Missing required environment variables: ${missing.join(', ')}\nAuthentication may not work correctly. Please set these in your .env file:\n${missing.map((v) => ` - ${v}`).join('\n')}`,
|
|
125
|
-
);
|
|
126
|
-
} else {
|
|
127
|
-
logger.info('All required environment variables are present');
|
|
74
|
+
logger.warn(`Missing env vars: ${missing.join(', ')}`);
|
|
128
75
|
}
|
|
129
76
|
}
|
|
130
77
|
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
updateConfig({
|
|
140
|
-
vite: {
|
|
141
|
-
plugins: [virtualModulePlugin],
|
|
142
|
-
},
|
|
143
|
-
});
|
|
144
|
-
} catch (error) {
|
|
145
|
-
const errorMsg =
|
|
146
|
-
error instanceof Error ? error.message : 'Unknown error';
|
|
147
|
-
logger.error(
|
|
148
|
-
`Failed to create virtual module for route configuration: ${errorMsg}`,
|
|
149
|
-
);
|
|
150
|
-
throw error;
|
|
78
|
+
// Create Vite virtual module plugin
|
|
79
|
+
const virtualModulePlugin = createVirtualModulePlugin(auth, loginPage, amplify || null);
|
|
80
|
+
const vitePlugins: any[] = [virtualModulePlugin];
|
|
81
|
+
|
|
82
|
+
if (import.meta.env?.DEV !== false) {
|
|
83
|
+
vitePlugins.push(vueDevTools());
|
|
84
|
+
logger.info('Vue DevTools enabled');
|
|
151
85
|
}
|
|
152
86
|
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
addMiddleware({
|
|
156
|
-
entrypoint: '@htlkg/astro/middleware',
|
|
157
|
-
order: 'pre',
|
|
158
|
-
});
|
|
159
|
-
logger.info('Authentication middleware injected successfully');
|
|
160
|
-
} catch (error) {
|
|
161
|
-
const errorMsg =
|
|
162
|
-
error instanceof Error ? error.message : 'Unknown error';
|
|
163
|
-
logger.error(`Failed to inject middleware: ${errorMsg}`);
|
|
164
|
-
throw error;
|
|
165
|
-
}
|
|
87
|
+
updateConfig({ vite: { plugins: vitePlugins } });
|
|
166
88
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (vueIntegrationIndex === -1) {
|
|
173
|
-
logger.warn(
|
|
174
|
-
'@astrojs/vue integration not found.\n' +
|
|
175
|
-
'The htlkg integration should have added it automatically.\n' +
|
|
176
|
-
'If you see this warning, there may be an integration ordering issue.',
|
|
177
|
-
);
|
|
178
|
-
} else {
|
|
179
|
-
logger.info('Vue app setup with Amplify configuration enabled');
|
|
89
|
+
// Inject middleware (only for full setup)
|
|
90
|
+
if (useFullVueSetup) {
|
|
91
|
+
addMiddleware({ entrypoint: '@htlkg/astro/middleware', order: 'pre' });
|
|
92
|
+
logger.info('Authentication middleware injected');
|
|
180
93
|
}
|
|
181
94
|
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
// 8. Inject login page route if enabled
|
|
193
|
-
if (loginPage !== false) {
|
|
194
|
-
try {
|
|
195
|
-
const loginPath = loginPage.path || '/login';
|
|
196
|
-
injectRoute({
|
|
197
|
-
pattern: loginPath,
|
|
198
|
-
entrypoint: '@htlkg/astro/auth/LoginPage.astro',
|
|
199
|
-
prerender: false,
|
|
200
|
-
});
|
|
201
|
-
logger.info(`Injected default login page at ${loginPath}`);
|
|
202
|
-
} catch (error) {
|
|
203
|
-
const errorMsg =
|
|
204
|
-
error instanceof Error ? error.message : 'Unknown error';
|
|
205
|
-
logger.warn(`Failed to inject login page: ${errorMsg}`);
|
|
206
|
-
}
|
|
95
|
+
// Inject login page (only for full setup)
|
|
96
|
+
if (loginPage !== false && useFullVueSetup) {
|
|
97
|
+
const loginPath = loginPage.path || '/login';
|
|
98
|
+
injectRoute({
|
|
99
|
+
pattern: loginPath,
|
|
100
|
+
entrypoint: '@htlkg/astro/auth/LoginPage.astro',
|
|
101
|
+
prerender: false,
|
|
102
|
+
});
|
|
103
|
+
logger.info(`Login page injected at ${loginPath}`);
|
|
207
104
|
}
|
|
208
105
|
|
|
209
|
-
logger.info('htlkg integration configured
|
|
106
|
+
logger.info('htlkg integration configured');
|
|
210
107
|
} catch (error) {
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
logger.error(
|
|
214
|
-
`Failed to configure htlkg integration: ${errorMsg}`,
|
|
215
|
-
);
|
|
108
|
+
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
109
|
+
logger.error(`htlkg configuration failed: ${msg}`);
|
|
216
110
|
throw error;
|
|
217
111
|
}
|
|
218
112
|
},
|
|
219
113
|
'astro:config:done': ({ injectTypes }) => {
|
|
220
|
-
// Inject TypeScript types for Astro.locals and virtual module
|
|
221
114
|
injectTypes({
|
|
222
115
|
filename: 'htlkg.d.ts',
|
|
223
116
|
content: `
|
|
@@ -240,6 +133,5 @@ export {};
|
|
|
240
133
|
},
|
|
241
134
|
});
|
|
242
135
|
|
|
243
|
-
|
|
244
|
-
return integrations.length === 1 ? integrations[0] : integrations;
|
|
136
|
+
return integrations;
|
|
245
137
|
}
|
package/src/index.ts
CHANGED
|
@@ -32,6 +32,9 @@ export { isAuthenticatedUser } from './htlkg/config.js';
|
|
|
32
32
|
// Utils
|
|
33
33
|
export * from './utils';
|
|
34
34
|
|
|
35
|
+
// Page Factories
|
|
36
|
+
export * from './factories';
|
|
37
|
+
|
|
35
38
|
// Middleware is not exported from main index to avoid bundling astro:middleware
|
|
36
39
|
// It's loaded by Astro at runtime via the entrypoint specified in addMiddleware
|
|
37
40
|
// Import from '@htlkg/astro/middleware' if needed
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Admin Layout
|
|
4
4
|
*
|
|
5
|
-
* Admin layout using
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* This layout will be fully implemented in a future task.
|
|
9
|
-
* For now, it provides a basic structure.
|
|
5
|
+
* Admin layout using AdminWrapper component with uiWrapper, sidebar, and topbar.
|
|
6
|
+
* Simple admin layout without authentication - pages handle their own auth.
|
|
7
|
+
* Uses nanostores for user data (no props needed for user).
|
|
10
8
|
*/
|
|
11
9
|
|
|
10
|
+
import { AdminWrapper, setUser } from "@htlkg/components";
|
|
11
|
+
import { uiViewHeader as UiViewHeader } from "@hotelinking/ui";
|
|
12
|
+
|
|
12
13
|
interface User {
|
|
13
14
|
username?: string;
|
|
14
15
|
email?: string;
|
|
@@ -18,12 +19,6 @@ interface User {
|
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
interface BreadcrumbItem {
|
|
22
|
-
label: string;
|
|
23
|
-
routeName: string;
|
|
24
|
-
current?: boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
22
|
interface SidebarItem {
|
|
28
23
|
name: string;
|
|
29
24
|
routeName: string;
|
|
@@ -42,51 +37,86 @@ interface SelectItem {
|
|
|
42
37
|
id: string;
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
interface
|
|
40
|
+
interface BreadcrumbPage {
|
|
41
|
+
name: string;
|
|
42
|
+
routeName: string;
|
|
43
|
+
current?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface HeaderProps {
|
|
46
47
|
title: string;
|
|
48
|
+
subtitle?: string;
|
|
47
49
|
description?: string;
|
|
50
|
+
breadcrumbs?: BreadcrumbPage[];
|
|
51
|
+
button?: {
|
|
52
|
+
text: string;
|
|
53
|
+
color?: "primary" | "secondary" | "red" | "yellow" | "green";
|
|
54
|
+
size?: "small" | "medium" | "big";
|
|
55
|
+
loading?: boolean;
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface SidebarProps {
|
|
61
|
+
logo: string;
|
|
62
|
+
title: string;
|
|
63
|
+
items: SidebarItem[];
|
|
48
64
|
currentPage?: string;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
sidebarItems?: SidebarItem[];
|
|
55
|
-
topbarActions?: TopbarAction[];
|
|
65
|
+
openByDefault?: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface TopbarProps {
|
|
69
|
+
actions?: TopbarAction[];
|
|
56
70
|
selectItems?: SelectItem[];
|
|
57
71
|
selectedItem?: SelectItem;
|
|
58
|
-
sidebarOpenByDefault?: boolean;
|
|
59
72
|
}
|
|
60
73
|
|
|
74
|
+
interface Props {
|
|
75
|
+
header: HeaderProps;
|
|
76
|
+
sidebar: SidebarProps;
|
|
77
|
+
topbar?: TopbarProps;
|
|
78
|
+
user?: User;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const { header, sidebar, topbar, user } = Astro.props;
|
|
82
|
+
|
|
83
|
+
// Extract header props
|
|
84
|
+
const { title, subtitle, description, breadcrumbs = [], button } = header;
|
|
85
|
+
|
|
86
|
+
// Extract sidebar props
|
|
61
87
|
const {
|
|
62
|
-
|
|
63
|
-
|
|
88
|
+
logo: sidebarLogo,
|
|
89
|
+
title: sidebarTitle,
|
|
90
|
+
items: sidebarItems,
|
|
64
91
|
currentPage = "dashboard",
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
topbarActions = [],
|
|
92
|
+
openByDefault: sidebarOpenByDefault = true,
|
|
93
|
+
} = sidebar;
|
|
94
|
+
|
|
95
|
+
// Extract topbar props
|
|
96
|
+
const {
|
|
97
|
+
actions: topbarActions = [],
|
|
72
98
|
selectItems = [],
|
|
73
99
|
selectedItem = { name: "", id: "" },
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const userData =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
} = topbar || {};
|
|
101
|
+
|
|
102
|
+
// Initialize user nanostore (server-side)
|
|
103
|
+
if (user) {
|
|
104
|
+
const userData = {
|
|
105
|
+
username: user.username || "Admin",
|
|
106
|
+
email: user.attributes?.email || user.email || "admin@example.com",
|
|
107
|
+
avatar: `https://ui-avatars.com/api/?name=${encodeURIComponent(user.username || "Admin")}&background=3B82F6&color=fff`,
|
|
108
|
+
isAdmin: user.isAdmin,
|
|
109
|
+
attributes: user.attributes,
|
|
110
|
+
};
|
|
111
|
+
setUser(userData);
|
|
112
|
+
} else {
|
|
113
|
+
setUser({
|
|
114
|
+
username: "Admin",
|
|
115
|
+
email: "admin@example.com",
|
|
116
|
+
avatar:
|
|
117
|
+
"https://ui-avatars.com/api/?name=Admin&background=3B82F6&color=fff",
|
|
118
|
+
});
|
|
119
|
+
}
|
|
90
120
|
|
|
91
121
|
// Build products sidebar based on user admin status
|
|
92
122
|
const baseProducts = [
|
|
@@ -130,55 +160,36 @@ import { ClientRouter } from "astro:transitions";
|
|
|
130
160
|
</head>
|
|
131
161
|
<body>
|
|
132
162
|
<div id="app">
|
|
133
|
-
<!--
|
|
134
|
-
<!--
|
|
163
|
+
<!-- Admin Wrapper with uiWrapper, sidebar, and topbar -->
|
|
164
|
+
<!-- User data comes from nanostore (no props needed!) -->
|
|
165
|
+
<AdminWrapper
|
|
166
|
+
client:load
|
|
167
|
+
sidebarLogo={sidebarLogo}
|
|
168
|
+
currentPage={currentPage}
|
|
169
|
+
sidebarTitle={sidebarTitle}
|
|
170
|
+
sidebarItems={sidebarItems}
|
|
171
|
+
topbarActions={topbarActions}
|
|
172
|
+
selectItems={selectItems}
|
|
173
|
+
selectedItem={selectedItem}
|
|
174
|
+
productsSidebar={productsSidebar}
|
|
175
|
+
sidebarOpenByDefault={sidebarOpenByDefault}
|
|
176
|
+
>
|
|
177
|
+
<!-- View Header with breadcrumbs, title, and optional button -->
|
|
178
|
+
<UiViewHeader
|
|
179
|
+
client:load
|
|
180
|
+
pages={breadcrumbs}
|
|
181
|
+
title={title}
|
|
182
|
+
subtitle={subtitle}
|
|
183
|
+
description={description}
|
|
184
|
+
buttonText={button?.text}
|
|
185
|
+
buttonColor={button?.color || "primary"}
|
|
186
|
+
buttonSize={button?.size || "medium"}
|
|
187
|
+
buttonLoading={button?.loading || false}
|
|
188
|
+
buttonDisabled={button?.disabled || false}
|
|
189
|
+
/>
|
|
135
190
|
|
|
136
|
-
<!-- Breadcrumbs Section (if enabled) -->
|
|
137
|
-
{
|
|
138
|
-
showBreadcrumbs && breadcrumbs.length > 0 && (
|
|
139
|
-
<div class="breadcrumbs-section mb-6 px-6 pt-6">
|
|
140
|
-
<nav class="flex" aria-label="Breadcrumb">
|
|
141
|
-
{breadcrumbs.map((crumb, index) => (
|
|
142
|
-
<>
|
|
143
|
-
{crumb.routeName ? (
|
|
144
|
-
<a
|
|
145
|
-
href={crumb.routeName}
|
|
146
|
-
class="text-gray-500 hover:text-gray-700"
|
|
147
|
-
>
|
|
148
|
-
{crumb.label}
|
|
149
|
-
</a>
|
|
150
|
-
) : (
|
|
151
|
-
<span class="text-gray-900">{crumb.label}</span>
|
|
152
|
-
)}
|
|
153
|
-
{index < breadcrumbs.length - 1 && (
|
|
154
|
-
<span class="mx-2 text-gray-400">/</span>
|
|
155
|
-
)}
|
|
156
|
-
</>
|
|
157
|
-
))}
|
|
158
|
-
</nav>
|
|
159
|
-
</div>
|
|
160
|
-
)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
<!-- Page Header -->
|
|
164
|
-
<div class="px-6 mb-6">
|
|
165
|
-
<div class="flex items-center justify-between">
|
|
166
|
-
<div>
|
|
167
|
-
<h1 class="text-2xl font-bold leading-7 sm:text-4xl sm:truncate">
|
|
168
|
-
{title}
|
|
169
|
-
</h1>
|
|
170
|
-
{description && <p class="text-gray-600 mt-2">{description}</p>}
|
|
171
|
-
</div>
|
|
172
|
-
<div class="flex items-center gap-3">
|
|
173
|
-
<slot name="actions" />
|
|
174
|
-
</div>
|
|
175
|
-
</div>
|
|
176
|
-
</div>
|
|
177
|
-
|
|
178
|
-
<!-- Page Content -->
|
|
179
|
-
<div class="px-6">
|
|
180
191
|
<slot />
|
|
181
|
-
</
|
|
192
|
+
</AdminWrapper>
|
|
182
193
|
</div>
|
|
183
194
|
</body>
|
|
184
195
|
</html>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Layouts Module
|
|
2
|
+
|
|
3
|
+
Pre-built page layouts for common page types.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { AdminLayout } from '@htlkg/astro/layouts';
|
|
9
|
+
import { BrandLayout } from '@htlkg/astro/layouts';
|
|
10
|
+
import { AuthLayout } from '@htlkg/astro/layouts';
|
|
11
|
+
import { PublicLayout } from '@htlkg/astro/layouts';
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## AdminLayout
|
|
15
|
+
|
|
16
|
+
Admin portal pages with sidebar navigation.
|
|
17
|
+
|
|
18
|
+
```astro
|
|
19
|
+
---
|
|
20
|
+
import { AdminLayout } from '@htlkg/astro/layouts';
|
|
21
|
+
|
|
22
|
+
const user = Astro.locals.user;
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<AdminLayout user={user} title="Dashboard">
|
|
26
|
+
<DashboardContent />
|
|
27
|
+
</AdminLayout>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## BrandLayout
|
|
31
|
+
|
|
32
|
+
Brand-specific pages with brand context.
|
|
33
|
+
|
|
34
|
+
```astro
|
|
35
|
+
---
|
|
36
|
+
import { BrandLayout } from '@htlkg/astro/layouts';
|
|
37
|
+
|
|
38
|
+
const { brandId } = Astro.params;
|
|
39
|
+
const brand = await getBrand(brandId);
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
<BrandLayout brand={brand} title="Brand Settings">
|
|
43
|
+
<BrandSettings />
|
|
44
|
+
</BrandLayout>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## AuthLayout
|
|
48
|
+
|
|
49
|
+
Authentication pages (login, signup).
|
|
50
|
+
|
|
51
|
+
```astro
|
|
52
|
+
---
|
|
53
|
+
import { AuthLayout } from '@htlkg/astro/layouts';
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
<AuthLayout title="Login">
|
|
57
|
+
<LoginForm />
|
|
58
|
+
</AuthLayout>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## PublicLayout
|
|
62
|
+
|
|
63
|
+
Public-facing pages without authentication.
|
|
64
|
+
|
|
65
|
+
```astro
|
|
66
|
+
---
|
|
67
|
+
import { PublicLayout } from '@htlkg/astro/layouts';
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
<PublicLayout title="Welcome">
|
|
71
|
+
<LandingPage />
|
|
72
|
+
</PublicLayout>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## DefaultLayout
|
|
76
|
+
|
|
77
|
+
Base layout with minimal styling.
|
|
78
|
+
|
|
79
|
+
```astro
|
|
80
|
+
---
|
|
81
|
+
import { DefaultLayout } from '@htlkg/astro/layouts';
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
<DefaultLayout title="Page Title">
|
|
85
|
+
<slot />
|
|
86
|
+
</DefaultLayout>
|
|
87
|
+
```
|
package/src/middleware/auth.ts
CHANGED
|
@@ -2,13 +2,52 @@
|
|
|
2
2
|
* Authentication middleware for htlkg integration
|
|
3
3
|
*
|
|
4
4
|
* This middleware:
|
|
5
|
+
* - Configures AWS Amplify for server-side auth
|
|
5
6
|
* - Retrieves the authenticated user from AWS Amplify
|
|
6
7
|
* - Injects the user into Astro.locals for use in pages and API routes
|
|
7
8
|
* - Handles authentication errors gracefully
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import type { MiddlewareHandler } from 'astro';
|
|
12
|
+
import { Amplify } from 'aws-amplify';
|
|
11
13
|
import { getUser } from '@htlkg/core/auth';
|
|
14
|
+
import { globalSettings } from '@htlkg/core/amplify-astro-adapter';
|
|
15
|
+
import { amplifyConfig } from 'virtual:htlkg-config';
|
|
16
|
+
|
|
17
|
+
// Track if Amplify has been configured
|
|
18
|
+
let amplifyConfigured = false;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Configure Amplify on first request
|
|
22
|
+
*/
|
|
23
|
+
function ensureAmplifyConfigured(): void {
|
|
24
|
+
if (amplifyConfigured) return;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
if (!amplifyConfig) {
|
|
28
|
+
console.warn('[htlkg Auth] No Amplify configuration provided');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Configure Amplify with SSR support
|
|
33
|
+
Amplify.configure(amplifyConfig as any, { ssr: true });
|
|
34
|
+
|
|
35
|
+
// Enable server-side auth in the adapter
|
|
36
|
+
globalSettings.enableServerSideAuth();
|
|
37
|
+
|
|
38
|
+
// Check if we're on HTTPS (for secure cookies)
|
|
39
|
+
const isSSL = typeof window !== 'undefined'
|
|
40
|
+
? window.location.protocol === 'https:'
|
|
41
|
+
: process.env.NODE_ENV === 'production';
|
|
42
|
+
globalSettings.setIsSSLOrigin(isSSL);
|
|
43
|
+
|
|
44
|
+
amplifyConfigured = true;
|
|
45
|
+
console.info('[htlkg Auth] Amplify configured for server-side auth');
|
|
46
|
+
} catch (error) {
|
|
47
|
+
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
48
|
+
console.error(`[htlkg Auth] Failed to configure Amplify: ${errorMsg}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
12
51
|
|
|
13
52
|
/**
|
|
14
53
|
* Auth middleware - retrieves authenticated user and injects into locals
|
|
@@ -27,6 +66,9 @@ import { getUser } from '@htlkg/core/auth';
|
|
|
27
66
|
export const authMiddleware: MiddlewareHandler = async (context, next) => {
|
|
28
67
|
const { locals } = context;
|
|
29
68
|
|
|
69
|
+
// Ensure Amplify is configured before attempting auth
|
|
70
|
+
ensureAmplifyConfigured();
|
|
71
|
+
|
|
30
72
|
try {
|
|
31
73
|
const user = await getUser(context);
|
|
32
74
|
locals.user = user;
|