master-page-vue 1.0.0

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 ADDED
@@ -0,0 +1,59 @@
1
+ # master-page-vue
2
+
3
+ Master page layout with Header, Sidebar Menu and Footer for Vue 3, with OIDC authentication via `oidc-client-ts`.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install master-page-vue oidc-client-ts
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ // main.ts
15
+ import { createApp } from 'vue';
16
+ import App from './App.vue';
17
+ import MasterPagePlugin from 'master-page-vue';
18
+
19
+ createApp(App)
20
+ .use(MasterPagePlugin, {
21
+ oidc: {
22
+ authority: 'https://identity.example.com',
23
+ clientId: 'mi-app',
24
+ },
25
+ appTitle: 'SISPRO - Mi Aplicación',
26
+ })
27
+ .mount('#app');
28
+ ```
29
+
30
+ ```vue
31
+ <!-- App.vue -->
32
+ <template>
33
+ <MasterPage>
34
+ <RouterView />
35
+ </MasterPage>
36
+ </template>
37
+ ```
38
+
39
+ ## Config options
40
+
41
+ | Option | Type | Description |
42
+ |--------|------|-------------|
43
+ | `oidc.authority` | `string` | Identity server URL |
44
+ | `oidc.clientId` | `string` | OAuth client ID |
45
+ | `oidc.clientSecret` | `string?` | OAuth client secret |
46
+ | `oidc.redirectUri` | `string?` | Redirect URI after login |
47
+ | `oidc.postLogoutRedirectUri` | `string?` | Redirect URI after logout |
48
+ | `oidc.scope` | `string?` | OAuth scopes |
49
+ | `appTitle` | `string?` | Title shown in header |
50
+ | `enableFetchInterceptor` | `boolean?` | Auto-inject bearer token in fetch calls |
51
+ | `registerUrl` | `string?` | Registration URL |
52
+
53
+ ## Composable
54
+
55
+ ```ts
56
+ import { useAuth } from 'master-page-vue';
57
+
58
+ const { isAuthenticated, userProfile, menuItems, login, logout } = useAuth();
59
+ ```
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,6 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
2
+ "menu-toggle": () => any;
3
+ }, string, import("vue").PublicProps, Readonly<{}> & Readonly<{
4
+ "onMenu-toggle"?: (() => any) | undefined;
5
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
6
+ export default _default;
@@ -0,0 +1,5 @@
1
+ type __VLS_Props = {
2
+ isOpen: boolean;
3
+ };
4
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
+ export default _default;
@@ -0,0 +1,12 @@
1
+ declare var __VLS_11: {};
2
+ type __VLS_Slots = {} & {
3
+ default?: (props: typeof __VLS_11) => any;
4
+ };
5
+ declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
6
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
7
+ export default _default;
8
+ type __VLS_WithSlots<T, S> = T & {
9
+ new (): {
10
+ $slots: S;
11
+ };
12
+ };
@@ -0,0 +1,46 @@
1
+ import type { MasterPageConfig } from '../models/types';
2
+ export declare function useAuth(): {
3
+ isAuthenticated: Readonly<import("vue").Ref<boolean, boolean>>;
4
+ userProfile: Readonly<import("vue").Ref<{
5
+ readonly firstName: string;
6
+ readonly middleName: string;
7
+ readonly lastName: string;
8
+ readonly secondLastName: string;
9
+ readonly enterpriseName: string;
10
+ readonly displayName: string;
11
+ readonly email?: string | undefined;
12
+ readonly accessToken: string;
13
+ readonly rawClaims: {
14
+ readonly [x: string]: Readonly<unknown>;
15
+ };
16
+ } | null, {
17
+ readonly firstName: string;
18
+ readonly middleName: string;
19
+ readonly lastName: string;
20
+ readonly secondLastName: string;
21
+ readonly enterpriseName: string;
22
+ readonly displayName: string;
23
+ readonly email?: string | undefined;
24
+ readonly accessToken: string;
25
+ readonly rawClaims: {
26
+ readonly [x: string]: Readonly<unknown>;
27
+ };
28
+ } | null>>;
29
+ menuItems: Readonly<import("vue").Ref<readonly {
30
+ readonly nombre: string;
31
+ readonly url?: string | undefined;
32
+ readonly hijos?: readonly /*elided*/ any[] | undefined;
33
+ }[], readonly {
34
+ readonly nombre: string;
35
+ readonly url?: string | undefined;
36
+ readonly hijos?: readonly /*elided*/ any[] | undefined;
37
+ }[]>>;
38
+ isLoading: Readonly<import("vue").Ref<boolean, boolean>>;
39
+ configure: (cfg: MasterPageConfig) => void;
40
+ getConfig: () => MasterPageConfig;
41
+ login: () => void;
42
+ logout: () => void;
43
+ openAccountSettings: () => void;
44
+ getAccessToken: () => Promise<string | null>;
45
+ destroyFetchInterceptor: () => void;
46
+ };
@@ -0,0 +1,15 @@
1
+ import type { App } from 'vue';
2
+ import type { MasterPageConfig } from './models/types';
3
+ import { useAuth } from './composables/useAuth';
4
+ import MasterPage from './components/MasterPage.vue';
5
+ import AppHeader from './components/AppHeader.vue';
6
+ import AppFooter from './components/AppFooter.vue';
7
+ import AppMenu from './components/AppMenu.vue';
8
+ export { MasterPage, AppHeader, AppFooter, AppMenu };
9
+ export { useAuth };
10
+ export type { MasterPageConfig, UserProfile, MenuItemConfig } from './models/types';
11
+ export type { OidcConfig } from './models/types';
12
+ declare const _default: {
13
+ install(app: App, config: MasterPageConfig): void;
14
+ };
15
+ export default _default;
@@ -0,0 +1,355 @@
1
+ import { ref as A, readonly as I, defineComponent as x, openBlock as r, createElementBlock as i, createElementVNode as t, unref as p, createCommentVNode as j, toDisplayString as $, createTextVNode as K, normalizeClass as L, Fragment as P, createStaticVNode as W, renderList as O, onUnmounted as q, createVNode as F, renderSlot as J } from "vue";
2
+ import { UserManager as Q } from "oidc-client-ts";
3
+ const M = A(!1), T = A(null), E = A([]), V = A(!0);
4
+ let k, b, y = null;
5
+ function R() {
6
+ function g(e) {
7
+ k = e;
8
+ const o = {
9
+ authority: e.oidc.authority,
10
+ client_id: e.oidc.clientId,
11
+ client_secret: e.oidc.clientSecret,
12
+ redirect_uri: e.oidc.redirectUri ?? `${window.location.origin}?op=2`,
13
+ response_type: e.oidc.responseType ?? "code",
14
+ scope: e.oidc.scope ?? "openid profile email roles offline_access",
15
+ post_logout_redirect_uri: e.oidc.postLogoutRedirectUri ?? `${window.location.origin}?op=1`
16
+ };
17
+ b = new Q(o), a(), C();
18
+ }
19
+ function l() {
20
+ return k;
21
+ }
22
+ function n() {
23
+ b.signinRedirect({ prompt: "login" });
24
+ }
25
+ function f() {
26
+ const e = localStorage.getItem("access_token") ?? "", o = encodeURIComponent(
27
+ k.oidc.postLogoutRedirectUri ?? `${window.location.origin}?op=1`
28
+ );
29
+ window.location.href = `${k.oidc.authority}/connect/logout?id_token_hint=${e}&post_logout_redirect_uri=${o}`;
30
+ }
31
+ function u() {
32
+ window.location.href = `${k.oidc.authority}/Identity/Account/Manage?salir=1`;
33
+ }
34
+ async function h() {
35
+ const e = await b.getUser();
36
+ return (e == null ? void 0 : e.access_token) ?? null;
37
+ }
38
+ function a() {
39
+ const e = new URLSearchParams(window.location.search).get("op");
40
+ e === "2" && b.signinRedirectCallback().then(() => {
41
+ window.location.href = "/";
42
+ }).catch((o) => console.error("[MasterPage] signin callback error:", o)), e === "1" && b.signoutRedirectCallback().then(() => {
43
+ b.removeUser(), window.location.href = "/";
44
+ }).catch((o) => console.error("[MasterPage] signout callback error:", o));
45
+ }
46
+ async function C() {
47
+ try {
48
+ const e = await b.getUser();
49
+ e && !e.expired ? (m(e), k.enableFetchInterceptor !== !1 && s(), await v(e)) : (M.value = !1, T.value = null);
50
+ } catch (e) {
51
+ console.error("[MasterPage] session check error:", e), M.value = !1;
52
+ } finally {
53
+ V.value = !1;
54
+ }
55
+ }
56
+ function m(e) {
57
+ const o = e.profile;
58
+ T.value = {
59
+ firstName: o.UserFirstName ?? "",
60
+ middleName: o.UserMiddleName ?? "",
61
+ lastName: o.UserLastName ?? "",
62
+ secondLastName: o.UserSecondLastName ?? "",
63
+ enterpriseName: o.EnterpriseName ?? "",
64
+ displayName: [o.UserFirstName, o.UserMiddleName, o.UserLastName, o.UserSecondLastName].filter(Boolean).join(" ").trim(),
65
+ email: o.email ?? "",
66
+ accessToken: e.access_token,
67
+ rawClaims: o
68
+ }, localStorage.setItem("access_token", e.access_token), M.value = !0;
69
+ }
70
+ async function v(e) {
71
+ var o;
72
+ try {
73
+ const c = await fetch(`${k.oidc.authority}/connect/userinfo`, {
74
+ headers: { Authorization: `Bearer ${e.access_token}` }
75
+ });
76
+ if (!c.ok) return;
77
+ const w = await c.json();
78
+ (o = w == null ? void 0 : w.usrAuthorizations) != null && o.aplicaciones && (E.value = w.usrAuthorizations.aplicaciones);
79
+ } catch (c) {
80
+ console.error("[MasterPage] Failed to fetch authorizations:", c);
81
+ }
82
+ }
83
+ function s() {
84
+ if (y) return;
85
+ y = window.fetch.bind(window);
86
+ const e = k.oidc.authority;
87
+ window.fetch = async (o, c = {}) => {
88
+ if ((typeof o == "string" ? o : o.url).startsWith(e)) return y(o, c);
89
+ let _ = await b.getUser();
90
+ _ && !_.expired && (c.headers = { ...c.headers, Authorization: `Bearer ${_.access_token}` });
91
+ let d = await y(o, c);
92
+ if (d.status === 401 && _)
93
+ try {
94
+ const N = await b.signinSilent();
95
+ N && (m(N), c.headers = { ...c.headers, Authorization: `Bearer ${N.access_token}` }, d = await y(o, c));
96
+ } catch {
97
+ await b.signoutRedirect();
98
+ }
99
+ return d;
100
+ };
101
+ }
102
+ function U() {
103
+ y && (window.fetch = y, y = null);
104
+ }
105
+ return {
106
+ isAuthenticated: I(M),
107
+ userProfile: I(T),
108
+ menuItems: I(E),
109
+ isLoading: I(V),
110
+ configure: g,
111
+ getConfig: l,
112
+ login: n,
113
+ logout: f,
114
+ openAccountSettings: u,
115
+ getAccessToken: h,
116
+ destroyFetchInterceptor: U
117
+ };
118
+ }
119
+ const X = { class: "navbar-custom" }, Y = { class: "container-fluid" }, Z = {
120
+ class: "navbar-brand",
121
+ href: "#"
122
+ }, ee = ["src"], oe = { class: "brand-text" }, te = { class: "navbar-right" }, ne = {
123
+ key: 0,
124
+ class: "nav-item"
125
+ }, se = { class: "user-label" }, ae = /* @__PURE__ */ x({
126
+ __name: "AppHeader",
127
+ emits: ["menu-toggle"],
128
+ setup(g, { emit: l }) {
129
+ var w;
130
+ const n = l, { isAuthenticated: f, userProfile: u, login: h, logout: a, openAccountSettings: C, getConfig: m } = R(), v = A(!1), s = m(), U = (s == null ? void 0 : s.appTitle) ?? "SISPRO - Aplicativos Misionales", e = ((w = s == null ? void 0 : s.oidc) == null ? void 0 : w.authority) ?? "";
131
+ function o(_) {
132
+ _.stopPropagation(), v.value = !v.value;
133
+ }
134
+ function c() {
135
+ var d;
136
+ const _ = (d = m()) == null ? void 0 : d.registerUrl;
137
+ _ && (window.location.href = _);
138
+ }
139
+ return (_, d) => {
140
+ var N, z;
141
+ return r(), i("nav", X, [
142
+ t("div", Y, [
143
+ p(f) ? (r(), i("button", {
144
+ key: 0,
145
+ class: "menu-toggle",
146
+ onClick: d[0] || (d[0] = (S) => n("menu-toggle")),
147
+ "aria-label": "Toggle menu"
148
+ }, " ☰ ")) : j("", !0),
149
+ t("a", Z, [
150
+ t("img", {
151
+ src: `${p(e)}/img/logo.svg`,
152
+ alt: "Logo gov.co"
153
+ }, null, 8, ee),
154
+ t("span", oe, $(p(U)), 1)
155
+ ]),
156
+ t("div", te, [
157
+ p(f) ? (r(), i("div", ne, [
158
+ t("a", {
159
+ class: "dropdown-user",
160
+ role: "button",
161
+ onClick: o
162
+ }, [
163
+ t("div", se, [
164
+ K($((N = p(u)) == null ? void 0 : N.displayName) + " ", 1),
165
+ t("span", null, $((z = p(u)) == null ? void 0 : z.enterpriseName), 1)
166
+ ]),
167
+ d[4] || (d[4] = t("button", { class: "user-icon-btn" }, [
168
+ t("svg", {
169
+ width: "24",
170
+ height: "24",
171
+ viewBox: "0 0 24 24",
172
+ fill: "none",
173
+ stroke: "currentColor",
174
+ "stroke-width": "2",
175
+ "stroke-linecap": "round",
176
+ "stroke-linejoin": "round"
177
+ }, [
178
+ t("path", { d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" }),
179
+ t("circle", {
180
+ cx: "12",
181
+ cy: "7",
182
+ r: "4"
183
+ })
184
+ ])
185
+ ], -1))
186
+ ]),
187
+ t("ul", {
188
+ class: L(["dropdown-menu", { open: v.value }])
189
+ }, [
190
+ t("li", null, [
191
+ t("a", {
192
+ onClick: d[1] || (d[1] = //@ts-ignore
193
+ (...S) => p(C) && p(C)(...S))
194
+ }, "Configuración")
195
+ ]),
196
+ t("li", null, [
197
+ t("a", {
198
+ onClick: d[2] || (d[2] = //@ts-ignore
199
+ (...S) => p(a) && p(a)(...S))
200
+ }, "Cerrar sesión")
201
+ ])
202
+ ], 2)
203
+ ])) : (r(), i(P, { key: 1 }, [
204
+ t("button", {
205
+ class: "btn-login",
206
+ onClick: d[3] || (d[3] = //@ts-ignore
207
+ (...S) => p(h) && p(h)(...S))
208
+ }, "Ingresar"),
209
+ t("button", {
210
+ class: "btn-register",
211
+ onClick: c
212
+ }, "Registrarse")
213
+ ], 64))
214
+ ])
215
+ ])
216
+ ]);
217
+ };
218
+ }
219
+ }), B = (g, l) => {
220
+ const n = g.__vccOpts || g;
221
+ for (const [f, u] of l)
222
+ n[f] = u;
223
+ return n;
224
+ }, D = /* @__PURE__ */ B(ae, [["__scopeId", "data-v-69067b97"]]), re = { class: "shell-footer" }, ie = { class: "container" }, ce = { class: "row" }, le = { class: "col logos-col" }, ue = ["src"], de = ["src"], fe = /* @__PURE__ */ x({
225
+ __name: "AppFooter",
226
+ setup(g) {
227
+ var f, u;
228
+ const { getConfig: l } = R(), n = ((u = (f = l()) == null ? void 0 : f.oidc) == null ? void 0 : u.authority) ?? "";
229
+ return (h, a) => (r(), i("footer", re, [
230
+ t("div", ie, [
231
+ t("div", ce, [
232
+ t("div", le, [
233
+ t("img", {
234
+ src: `${p(n)}/img/logo.svg`,
235
+ alt: "GOV.CO",
236
+ class: "footer-logo"
237
+ }, null, 8, ue),
238
+ t("img", {
239
+ src: `${p(n)}/img/colombia.png`,
240
+ alt: "Colombia Potencia de la Vida",
241
+ class: "footer-logo"
242
+ }, null, 8, de)
243
+ ]),
244
+ a[0] || (a[0] = W('<div class="col border-start" data-v-93fe5bbf><strong data-v-93fe5bbf>SISPRO</strong><p data-v-93fe5bbf> Dirección:<br data-v-93fe5bbf> Carrera 13 # 32–76 (piso 1)<br data-v-93fe5bbf> Cundinamarca, Bogotá D.C.<br data-v-93fe5bbf> Código Postal: 110311 </p><p data-v-93fe5bbf><a href="#" class="footer-link" data-v-93fe5bbf>Horario de atención:</a><br data-v-93fe5bbf> Lunes a viernes 8:00 a.m. a 4:00 p.m. </p></div><div class="col border-start" data-v-93fe5bbf><strong data-v-93fe5bbf>Contacto</strong><p data-v-93fe5bbf> Teléfono conmutador:<br data-v-93fe5bbf> En Bogotá: 601 330 5043 Opción 2<br data-v-93fe5bbf> Resto del país: 018000960020 Opción 2 </p><p data-v-93fe5bbf> Correo de notificaciones judiciales:<br data-v-93fe5bbf><a href="mailto:notificacionesjudiciales@minsalud.gov.co" class="footer-link" data-v-93fe5bbf> notificacionesjudiciales@minsalud.gov.co </a></p><p data-v-93fe5bbf><a href="https://www.minsalud.gov.co/Documents/Ministerio/Terminos%20y%20Condiciones%20de%20uso%20del%20portal%20web_Octubre%202012x.pdf" target="_blank" class="footer-link" data-v-93fe5bbf> Términos y condiciones </a></p></div>', 2))
245
+ ])
246
+ ])
247
+ ]));
248
+ }
249
+ }), H = /* @__PURE__ */ B(fe, [["__scopeId", "data-v-93fe5bbf"]]), pe = { class: "menu-list" }, ge = ["onClick"], he = ["href"], _e = {
250
+ key: 2,
251
+ class: "submenu"
252
+ }, be = ["onClick"], me = ["href"], ve = {
253
+ key: 2,
254
+ class: "submenu"
255
+ }, we = ["href"], ke = /* @__PURE__ */ x({
256
+ __name: "AppMenu",
257
+ props: {
258
+ isOpen: { type: Boolean }
259
+ },
260
+ setup(g) {
261
+ const { menuItems: l } = R(), n = A(/* @__PURE__ */ new Set());
262
+ function f(u) {
263
+ n.value.has(u) ? n.value.delete(u) : n.value.add(u), n.value = new Set(n.value);
264
+ }
265
+ return (u, h) => (r(), i("aside", {
266
+ class: L(["sidebar-menu", { show: g.isOpen }])
267
+ }, [
268
+ t("ul", pe, [
269
+ (r(!0), i(P, null, O(p(l), (a) => {
270
+ var C, m, v;
271
+ return r(), i("li", {
272
+ key: a.nombre,
273
+ class: L({ "has-submenu": (C = a.hijos) == null ? void 0 : C.length, open: n.value.has(a) })
274
+ }, [
275
+ (m = a.hijos) != null && m.length ? (r(), i("span", {
276
+ key: 0,
277
+ class: "has-children",
278
+ onClick: (s) => f(a)
279
+ }, $(a.nombre), 9, ge)) : (r(), i("a", {
280
+ key: 1,
281
+ href: a.url || "#"
282
+ }, $(a.nombre), 9, he)),
283
+ (v = a.hijos) != null && v.length && n.value.has(a) ? (r(), i("ul", _e, [
284
+ (r(!0), i(P, null, O(a.hijos, (s) => {
285
+ var U, e, o;
286
+ return r(), i("li", {
287
+ key: s.nombre,
288
+ class: L({ "has-submenu": (U = s.hijos) == null ? void 0 : U.length, open: n.value.has(s) })
289
+ }, [
290
+ (e = s.hijos) != null && e.length ? (r(), i("span", {
291
+ key: 0,
292
+ class: "has-children",
293
+ onClick: (c) => f(s)
294
+ }, $(s.nombre), 9, be)) : (r(), i("a", {
295
+ key: 1,
296
+ href: s.url || "#"
297
+ }, $(s.nombre), 9, me)),
298
+ (o = s.hijos) != null && o.length && n.value.has(s) ? (r(), i("ul", ve, [
299
+ (r(!0), i(P, null, O(s.hijos, (c) => (r(), i("li", {
300
+ key: c.nombre
301
+ }, [
302
+ t("a", {
303
+ href: c.url || "#"
304
+ }, $(c.nombre), 9, we)
305
+ ]))), 128))
306
+ ])) : j("", !0)
307
+ ], 2);
308
+ }), 128))
309
+ ])) : j("", !0)
310
+ ], 2);
311
+ }), 128))
312
+ ])
313
+ ], 2));
314
+ }
315
+ }), G = /* @__PURE__ */ B(ke, [["__scopeId", "data-v-1619503d"]]), ye = { class: "master-page-layout" }, $e = { class: "shell-main" }, Ce = /* @__PURE__ */ x({
316
+ __name: "MasterPage",
317
+ setup(g) {
318
+ const l = A(!1);
319
+ function n() {
320
+ l.value = !l.value;
321
+ }
322
+ function f() {
323
+ l.value = !1;
324
+ }
325
+ function u(h) {
326
+ h.key === "Escape" && f();
327
+ }
328
+ return window.addEventListener("keydown", u), q(() => window.removeEventListener("keydown", u)), (h, a) => (r(), i("div", ye, [
329
+ F(D, { onMenuToggle: n }),
330
+ F(G, { "is-open": l.value }, null, 8, ["is-open"]),
331
+ l.value ? (r(), i("div", {
332
+ key: 0,
333
+ class: "sidebar-backdrop",
334
+ onClick: f
335
+ })) : j("", !0),
336
+ t("main", $e, [
337
+ J(h.$slots, "default", {}, void 0, !0)
338
+ ]),
339
+ F(H)
340
+ ]));
341
+ }
342
+ }), Ae = /* @__PURE__ */ B(Ce, [["__scopeId", "data-v-ad151b79"]]), Se = {
343
+ install(g, l) {
344
+ const { configure: n } = R();
345
+ n(l), g.component("MasterPage", Ae), g.component("AppHeader", D), g.component("AppFooter", H), g.component("AppMenu", G);
346
+ }
347
+ };
348
+ export {
349
+ H as AppFooter,
350
+ D as AppHeader,
351
+ G as AppMenu,
352
+ Ae as MasterPage,
353
+ Se as default,
354
+ R as useAuth
355
+ };
@@ -0,0 +1 @@
1
+ (function(m,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue"),require("oidc-client-ts")):typeof define=="function"&&define.amd?define(["exports","vue","oidc-client-ts"],e):(m=typeof globalThis<"u"?globalThis:m||self,e(m.MasterPageVue={},m.Vue,m.OidcClientTs))})(this,function(m,e,F){"use strict";const V=e.ref(!1),S=e.ref(null),I=e.ref([]),P=e.ref(!0);let _,p,u=null;function C(){function f(o){_=o;const t={authority:o.oidc.authority,client_id:o.oidc.clientId,client_secret:o.oidc.clientSecret,redirect_uri:o.oidc.redirectUri??`${window.location.origin}?op=2`,response_type:o.oidc.responseType??"code",scope:o.oidc.scope??"openid profile email roles offline_access",post_logout_redirect_uri:o.oidc.postLogoutRedirectUri??`${window.location.origin}?op=1`};p=new F.UserManager(t),s(),y()}function c(){return _}function n(){p.signinRedirect({prompt:"login"})}function d(){const o=localStorage.getItem("access_token")??"",t=encodeURIComponent(_.oidc.postLogoutRedirectUri??`${window.location.origin}?op=1`);window.location.href=`${_.oidc.authority}/connect/logout?id_token_hint=${o}&post_logout_redirect_uri=${t}`}function i(){window.location.href=`${_.oidc.authority}/Identity/Account/Manage?salir=1`}async function g(){const o=await p.getUser();return(o==null?void 0:o.access_token)??null}function s(){const o=new URLSearchParams(window.location.search).get("op");o==="2"&&p.signinRedirectCallback().then(()=>{window.location.href="/"}).catch(t=>console.error("[MasterPage] signin callback error:",t)),o==="1"&&p.signoutRedirectCallback().then(()=>{p.removeUser(),window.location.href="/"}).catch(t=>console.error("[MasterPage] signout callback error:",t))}async function y(){try{const o=await p.getUser();o&&!o.expired?(b(o),_.enableFetchInterceptor!==!1&&a(),await k(o)):(V.value=!1,S.value=null)}catch(o){console.error("[MasterPage] session check error:",o),V.value=!1}finally{P.value=!1}}function b(o){const t=o.profile;S.value={firstName:t.UserFirstName??"",middleName:t.UserMiddleName??"",lastName:t.UserLastName??"",secondLastName:t.UserSecondLastName??"",enterpriseName:t.EnterpriseName??"",displayName:[t.UserFirstName,t.UserMiddleName,t.UserLastName,t.UserSecondLastName].filter(Boolean).join(" ").trim(),email:t.email??"",accessToken:o.access_token,rawClaims:t},localStorage.setItem("access_token",o.access_token),V.value=!0}async function k(o){var t;try{const r=await fetch(`${_.oidc.authority}/connect/userinfo`,{headers:{Authorization:`Bearer ${o.access_token}`}});if(!r.ok)return;const w=await r.json();(t=w==null?void 0:w.usrAuthorizations)!=null&&t.aplicaciones&&(I.value=w.usrAuthorizations.aplicaciones)}catch(r){console.error("[MasterPage] Failed to fetch authorizations:",r)}}function a(){if(u)return;u=window.fetch.bind(window);const o=_.oidc.authority;window.fetch=async(t,r={})=>{if((typeof t=="string"?t:t.url).startsWith(o))return u(t,r);let h=await p.getUser();h&&!h.expired&&(r.headers={...r.headers,Authorization:`Bearer ${h.access_token}`});let l=await u(t,r);if(l.status===401&&h)try{const E=await p.signinSilent();E&&(b(E),r.headers={...r.headers,Authorization:`Bearer ${E.access_token}`},l=await u(t,r))}catch{await p.signoutRedirect()}return l}}function N(){u&&(window.fetch=u,u=null)}return{isAuthenticated:e.readonly(V),userProfile:e.readonly(S),menuItems:e.readonly(I),isLoading:e.readonly(P),configure:f,getConfig:c,login:n,logout:d,openAccountSettings:i,getAccessToken:g,destroyFetchInterceptor:N}}const T={class:"navbar-custom"},O={class:"container-fluid"},R={class:"navbar-brand",href:"#"},D=["src"],z={class:"brand-text"},x={class:"navbar-right"},H={key:0,class:"nav-item"},q={class:"user-label"},G=e.defineComponent({__name:"AppHeader",emits:["menu-toggle"],setup(f,{emit:c}){var w;const n=c,{isAuthenticated:d,userProfile:i,login:g,logout:s,openAccountSettings:y,getConfig:b}=C(),k=e.ref(!1),a=b(),N=(a==null?void 0:a.appTitle)??"SISPRO - Aplicativos Misionales",o=((w=a==null?void 0:a.oidc)==null?void 0:w.authority)??"";function t(h){h.stopPropagation(),k.value=!k.value}function r(){var l;const h=(l=b())==null?void 0:l.registerUrl;h&&(window.location.href=h)}return(h,l)=>{var E,j;return e.openBlock(),e.createElementBlock("nav",T,[e.createElementVNode("div",O,[e.unref(d)?(e.openBlock(),e.createElementBlock("button",{key:0,class:"menu-toggle",onClick:l[0]||(l[0]=B=>n("menu-toggle")),"aria-label":"Toggle menu"}," ☰ ")):e.createCommentVNode("",!0),e.createElementVNode("a",R,[e.createElementVNode("img",{src:`${e.unref(o)}/img/logo.svg`,alt:"Logo gov.co"},null,8,D),e.createElementVNode("span",z,e.toDisplayString(e.unref(N)),1)]),e.createElementVNode("div",x,[e.unref(d)?(e.openBlock(),e.createElementBlock("div",H,[e.createElementVNode("a",{class:"dropdown-user",role:"button",onClick:t},[e.createElementVNode("div",q,[e.createTextVNode(e.toDisplayString((E=e.unref(i))==null?void 0:E.displayName)+" ",1),e.createElementVNode("span",null,e.toDisplayString((j=e.unref(i))==null?void 0:j.enterpriseName),1)]),l[4]||(l[4]=e.createElementVNode("button",{class:"user-icon-btn"},[e.createElementVNode("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},[e.createElementVNode("path",{d:"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"}),e.createElementVNode("circle",{cx:"12",cy:"7",r:"4"})])],-1))]),e.createElementVNode("ul",{class:e.normalizeClass(["dropdown-menu",{open:k.value}])},[e.createElementVNode("li",null,[e.createElementVNode("a",{onClick:l[1]||(l[1]=(...B)=>e.unref(y)&&e.unref(y)(...B))},"Configuración")]),e.createElementVNode("li",null,[e.createElementVNode("a",{onClick:l[2]||(l[2]=(...B)=>e.unref(s)&&e.unref(s)(...B))},"Cerrar sesión")])],2)])):(e.openBlock(),e.createElementBlock(e.Fragment,{key:1},[e.createElementVNode("button",{class:"btn-login",onClick:l[3]||(l[3]=(...B)=>e.unref(g)&&e.unref(g)(...B))},"Ingresar"),e.createElementVNode("button",{class:"btn-register",onClick:r},"Registrarse")],64))])])])}}}),$=(f,c)=>{const n=f.__vccOpts||f;for(const[d,i]of c)n[d]=i;return n},A=$(G,[["__scopeId","data-v-69067b97"]]),K={class:"shell-footer"},W={class:"container"},J={class:"row"},Q={class:"col logos-col"},X=["src"],Y=["src"],M=$(e.defineComponent({__name:"AppFooter",setup(f){var d,i;const{getConfig:c}=C(),n=((i=(d=c())==null?void 0:d.oidc)==null?void 0:i.authority)??"";return(g,s)=>(e.openBlock(),e.createElementBlock("footer",K,[e.createElementVNode("div",W,[e.createElementVNode("div",J,[e.createElementVNode("div",Q,[e.createElementVNode("img",{src:`${e.unref(n)}/img/logo.svg`,alt:"GOV.CO",class:"footer-logo"},null,8,X),e.createElementVNode("img",{src:`${e.unref(n)}/img/colombia.png`,alt:"Colombia Potencia de la Vida",class:"footer-logo"},null,8,Y)]),s[0]||(s[0]=e.createStaticVNode('<div class="col border-start" data-v-93fe5bbf><strong data-v-93fe5bbf>SISPRO</strong><p data-v-93fe5bbf> Dirección:<br data-v-93fe5bbf> Carrera 13 # 32–76 (piso 1)<br data-v-93fe5bbf> Cundinamarca, Bogotá D.C.<br data-v-93fe5bbf> Código Postal: 110311 </p><p data-v-93fe5bbf><a href="#" class="footer-link" data-v-93fe5bbf>Horario de atención:</a><br data-v-93fe5bbf> Lunes a viernes 8:00 a.m. a 4:00 p.m. </p></div><div class="col border-start" data-v-93fe5bbf><strong data-v-93fe5bbf>Contacto</strong><p data-v-93fe5bbf> Teléfono conmutador:<br data-v-93fe5bbf> En Bogotá: 601 330 5043 Opción 2<br data-v-93fe5bbf> Resto del país: 018000960020 Opción 2 </p><p data-v-93fe5bbf> Correo de notificaciones judiciales:<br data-v-93fe5bbf><a href="mailto:notificacionesjudiciales@minsalud.gov.co" class="footer-link" data-v-93fe5bbf> notificacionesjudiciales@minsalud.gov.co </a></p><p data-v-93fe5bbf><a href="https://www.minsalud.gov.co/Documents/Ministerio/Terminos%20y%20Condiciones%20de%20uso%20del%20portal%20web_Octubre%202012x.pdf" target="_blank" class="footer-link" data-v-93fe5bbf> Términos y condiciones </a></p></div>',2))])])]))}}),[["__scopeId","data-v-93fe5bbf"]]),Z={class:"menu-list"},v=["onClick"],ee=["href"],oe={key:2,class:"submenu"},te=["onClick"],ne=["href"],ae={key:2,class:"submenu"},se=["href"],U=$(e.defineComponent({__name:"AppMenu",props:{isOpen:{type:Boolean}},setup(f){const{menuItems:c}=C(),n=e.ref(new Set);function d(i){n.value.has(i)?n.value.delete(i):n.value.add(i),n.value=new Set(n.value)}return(i,g)=>(e.openBlock(),e.createElementBlock("aside",{class:e.normalizeClass(["sidebar-menu",{show:f.isOpen}])},[e.createElementVNode("ul",Z,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(e.unref(c),s=>{var y,b,k;return e.openBlock(),e.createElementBlock("li",{key:s.nombre,class:e.normalizeClass({"has-submenu":(y=s.hijos)==null?void 0:y.length,open:n.value.has(s)})},[(b=s.hijos)!=null&&b.length?(e.openBlock(),e.createElementBlock("span",{key:0,class:"has-children",onClick:a=>d(s)},e.toDisplayString(s.nombre),9,v)):(e.openBlock(),e.createElementBlock("a",{key:1,href:s.url||"#"},e.toDisplayString(s.nombre),9,ee)),(k=s.hijos)!=null&&k.length&&n.value.has(s)?(e.openBlock(),e.createElementBlock("ul",oe,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(s.hijos,a=>{var N,o,t;return e.openBlock(),e.createElementBlock("li",{key:a.nombre,class:e.normalizeClass({"has-submenu":(N=a.hijos)==null?void 0:N.length,open:n.value.has(a)})},[(o=a.hijos)!=null&&o.length?(e.openBlock(),e.createElementBlock("span",{key:0,class:"has-children",onClick:r=>d(a)},e.toDisplayString(a.nombre),9,te)):(e.openBlock(),e.createElementBlock("a",{key:1,href:a.url||"#"},e.toDisplayString(a.nombre),9,ne)),(t=a.hijos)!=null&&t.length&&n.value.has(a)?(e.openBlock(),e.createElementBlock("ul",ae,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(a.hijos,r=>(e.openBlock(),e.createElementBlock("li",{key:r.nombre},[e.createElementVNode("a",{href:r.url||"#"},e.toDisplayString(r.nombre),9,se)]))),128))])):e.createCommentVNode("",!0)],2)}),128))])):e.createCommentVNode("",!0)],2)}),128))])],2))}}),[["__scopeId","data-v-1619503d"]]),re={class:"master-page-layout"},ce={class:"shell-main"},L=$(e.defineComponent({__name:"MasterPage",setup(f){const c=e.ref(!1);function n(){c.value=!c.value}function d(){c.value=!1}function i(g){g.key==="Escape"&&d()}return window.addEventListener("keydown",i),e.onUnmounted(()=>window.removeEventListener("keydown",i)),(g,s)=>(e.openBlock(),e.createElementBlock("div",re,[e.createVNode(A,{onMenuToggle:n}),e.createVNode(U,{"is-open":c.value},null,8,["is-open"]),c.value?(e.openBlock(),e.createElementBlock("div",{key:0,class:"sidebar-backdrop",onClick:d})):e.createCommentVNode("",!0),e.createElementVNode("main",ce,[e.renderSlot(g.$slots,"default",{},void 0,!0)]),e.createVNode(M)]))}}),[["__scopeId","data-v-ad151b79"]]),ie={install(f,c){const{configure:n}=C();n(c),f.component("MasterPage",L),f.component("AppHeader",A),f.component("AppFooter",M),f.component("AppMenu",U)}};m.AppFooter=M,m.AppHeader=A,m.AppMenu=U,m.MasterPage=L,m.default=ie,m.useAuth=C,Object.defineProperties(m,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
@@ -0,0 +1,32 @@
1
+ export interface OidcConfig {
2
+ authority: string;
3
+ clientId: string;
4
+ clientSecret?: string;
5
+ redirectUri?: string;
6
+ postLogoutRedirectUri?: string;
7
+ scope?: string;
8
+ responseType?: string;
9
+ }
10
+ export interface MasterPageConfig {
11
+ oidc: OidcConfig;
12
+ appTitle?: string;
13
+ assetsBasePath?: string;
14
+ enableFetchInterceptor?: boolean;
15
+ registerUrl?: string;
16
+ }
17
+ export interface UserProfile {
18
+ firstName: string;
19
+ middleName: string;
20
+ lastName: string;
21
+ secondLastName: string;
22
+ enterpriseName: string;
23
+ displayName: string;
24
+ email?: string;
25
+ accessToken: string;
26
+ rawClaims: Record<string, unknown>;
27
+ }
28
+ export interface MenuItemConfig {
29
+ readonly nombre: string;
30
+ readonly url?: string;
31
+ readonly hijos?: readonly MenuItemConfig[];
32
+ }
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .navbar-custom[data-v-69067b97]{background-color:#36c;box-shadow:0 4px 10px #0003;position:fixed;width:100%;z-index:1001;height:60px}.container-fluid[data-v-69067b97]{display:flex;align-items:center;width:100%;padding:0 15px}.navbar-brand[data-v-69067b97]{color:#fff;display:flex;align-items:center;gap:10px;text-decoration:none}.navbar-brand img[data-v-69067b97]{height:36px;margin-top:1px}.brand-text[data-v-69067b97]{padding-top:5px}.navbar-right[data-v-69067b97]{display:flex;align-items:center;margin-left:auto}.nav-item[data-v-69067b97]{position:relative}.dropdown-user[data-v-69067b97]{display:flex;align-items:center;gap:.5rem;color:#fff;cursor:pointer;text-decoration:none}.user-label[data-v-69067b97]{text-align:right;font-weight:500;line-height:1.2;color:#fff}.user-label span[data-v-69067b97]{display:block;font-size:.875rem;color:#e0e0e0}.user-icon-btn[data-v-69067b97]{background:transparent;border:none;color:#fff;cursor:pointer;padding:0}.dropdown-menu[data-v-69067b97]{display:none;position:absolute;right:0;top:50px;min-width:200px;background:#fff;border:1px solid #ddd;box-shadow:0 2px 6px #0000001a;border-radius:10px;list-style:none;margin:0;padding:0;z-index:1002}.dropdown-menu.open[data-v-69067b97]{display:block}.dropdown-menu li a[data-v-69067b97]{display:block;padding:10px 15px;color:#333;cursor:pointer;text-decoration:none}.dropdown-menu li a[data-v-69067b97]:hover{background:#f5f5f5}.menu-toggle[data-v-69067b97]{font-size:30px;border:none;background:transparent;color:#fff;cursor:pointer;margin-right:15px;padding:0;line-height:1}.btn-login[data-v-69067b97],.btn-register[data-v-69067b97]{margin-left:10px;border-radius:20px;font-weight:500;border:1px solid white;padding:6px 16px;cursor:pointer}.btn-login[data-v-69067b97]{color:#fff;background:transparent}.btn-register[data-v-69067b97]{color:#36c;background:#fff}@media (max-width: 860px){.navbar-brand[data-v-69067b97]{display:none}}.shell-footer[data-v-93fe5bbf]{background-color:#36c;color:#fff;padding:1.5rem 0;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;font-size:1em;width:100%}.container[data-v-93fe5bbf]{max-width:1200px;margin:0 auto;padding:0 15px}.row[data-v-93fe5bbf]{display:flex;flex-wrap:wrap;align-items:flex-start}.col[data-v-93fe5bbf]{flex:1;padding:0 15px}.logos-col[data-v-93fe5bbf]{display:flex;align-items:center;justify-content:center;gap:16px}.footer-logo[data-v-93fe5bbf]{max-height:50px}.border-start[data-v-93fe5bbf]{border-left:1px solid rgba(255,255,255,.3)}.footer-link[data-v-93fe5bbf]{color:#fff;text-decoration:underline}strong[data-v-93fe5bbf]{display:block;margin-bottom:.5rem}p[data-v-93fe5bbf]{margin:.25rem 0;line-height:1.5}@media (max-width: 768px){.col[data-v-93fe5bbf]{flex:0 0 100%;margin-bottom:1rem}.border-start[data-v-93fe5bbf]{border-left:none}}.sidebar-menu[data-v-1619503d]{position:fixed;top:0;left:0;height:100vh;width:300px;background-color:#36c;padding-top:60px;transform:translate(-100%);transition:transform .3s ease;z-index:1000;overflow-y:auto}.sidebar-menu.show[data-v-1619503d]{transform:translate(0)}.menu-list[data-v-1619503d],.submenu[data-v-1619503d]{list-style:none;padding:0;margin:0}.submenu[data-v-1619503d]{padding-left:10px}li a[data-v-1619503d],li span[data-v-1619503d]{display:block;padding:10px;color:#fff;text-decoration:none;font-weight:500;cursor:pointer}li a[data-v-1619503d]:hover,li span[data-v-1619503d]:hover{background-color:#2952a3}.has-submenu>span.has-children[data-v-1619503d]:after{content:" ▼";font-size:.8em;float:right}.has-submenu.open>span.has-children[data-v-1619503d]:after{content:" ▲"}.master-page-layout[data-v-ad151b79]{display:flex;flex-direction:column;min-height:100vh;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif}.shell-main[data-v-ad151b79]{flex:1;padding-top:65px;min-height:calc(100vh - 260px)}.sidebar-backdrop[data-v-ad151b79]{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000059;z-index:999;cursor:pointer;transition:opacity .3s ease}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "master-page-vue",
3
+ "version": "1.0.0",
4
+ "description": "Master page layout with OIDC auth for Vue 3",
5
+ "main": "dist/master-page-vue.umd.cjs",
6
+ "module": "dist/master-page-vue.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/master-page-vue.js",
11
+ "require": "./dist/master-page-vue.umd.cjs",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": ["dist"],
16
+ "scripts": {
17
+ "build": "vite build && vue-tsc --emitDeclarationOnly",
18
+ "dev": "vite"
19
+ },
20
+ "peerDependencies": {
21
+ "vue": "^3.0.0"
22
+ },
23
+ "dependencies": {
24
+ "oidc-client-ts": "^3.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@vitejs/plugin-vue": "^5.0.0",
28
+ "typescript": "^5.0.0",
29
+ "vite": "^5.0.0",
30
+ "vite-plugin-dts": "^3.0.0",
31
+ "vue": "^3.4.0",
32
+ "vue-tsc": "^2.0.0"
33
+ }
34
+ }