@innertia-solutions/innertia-nuxt 0.1.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/.github/workflows/auto-publish.yml +64 -0
- package/.github/workflows/release.yml +59 -0
- package/README.md +60 -0
- package/app.config.ts +70 -0
- package/components/Admin/Base.vue +144 -0
- package/components/Admin/Header.vue +32 -0
- package/components/Admin/Page.vue +65 -0
- package/components/Admin/PageHeader.vue +31 -0
- package/components/App/Button.vue +59 -0
- package/components/App/DevEnvironmentBar.vue +43 -0
- package/components/App/Dropdown.vue +286 -0
- package/components/App/EmptyState.vue +433 -0
- package/components/App/LoadingState.vue +40 -0
- package/components/App/PageLoadingSpinner.vue +118 -0
- package/components/App/PreviewDock.vue +64 -0
- package/components/App/SwitchColorTheme.vue +51 -0
- package/components/App/Tag.vue +193 -0
- package/components/DataTable.vue +713 -0
- package/components/Forms/DatePicker.vue +255 -0
- package/components/Forms/Input.vue +75 -0
- package/components/Forms/Select.vue +100 -0
- package/components/Forms/SelectServer.vue +726 -0
- package/components/Layout/Admin.vue +32 -0
- package/components/Layout/Auth.vue +29 -0
- package/components/Layout/SidebarWithAppColumn.vue +388 -0
- package/components/Layout/TopBar.vue +113 -0
- package/components/MobileBlocker.vue +85 -0
- package/components/MobileLoginPicker.vue +83 -0
- package/components/Modal/Base.vue +29 -0
- package/components/Modal/DeleteConfirm.vue +48 -0
- package/components/Modal.vue +103 -0
- package/components/Nav/Tabs.vue +55 -0
- package/components/PermissionsTree.vue +272 -0
- package/components/Table/Database.vue +183 -0
- package/components/Table/DownloadDropdown.vue +111 -0
- package/components/Table/Enterprise.vue +540 -0
- package/components/Table/FilterDropdown.vue +226 -0
- package/components/Table/Grid.vue +62 -0
- package/components/Table/Kanban.vue +188 -0
- package/components/Table/List.vue +128 -0
- package/components/Table/PreviewTimeline.vue +118 -0
- package/components/Table/Standard.vue +1217 -0
- package/components/Table/index.vue +974 -0
- package/components/TableExportable.vue +172 -0
- package/components/TableFilter.vue +93 -0
- package/components/Toast/Alert.vue +113 -0
- package/components/Toast/Container.vue +34 -0
- package/components/Toast/Notification.vue +45 -0
- package/components/Toast/Process.vue +88 -0
- package/composables/useApi.js +95 -0
- package/composables/useApp.ts +46 -0
- package/composables/useAuth.js +82 -0
- package/composables/useContext.js +44 -0
- package/composables/useDate.js +241 -0
- package/composables/useDevice.js +21 -0
- package/composables/useDockedPreviews.js +56 -0
- package/composables/useDownload.js +87 -0
- package/composables/useEntity.js +82 -0
- package/composables/useForm.js +119 -0
- package/composables/useInnertiaMode.ts +25 -0
- package/composables/useMobileGuard.ts +81 -0
- package/composables/useNotifications.js +22 -0
- package/composables/usePermissions.js +23 -0
- package/composables/useRealtime.js +123 -0
- package/composables/useRequestInterceptors.js +27 -0
- package/composables/useRoles.js +53 -0
- package/composables/useRutFormatter.js +39 -0
- package/composables/useTable.ts +94 -0
- package/composables/useTablePreferences.ts +33 -0
- package/composables/useTenant.js +27 -0
- package/composables/useTimeAgo.js +37 -0
- package/composables/useToast.js +69 -0
- package/composables/useUserRealtime.js +17 -0
- package/composables/useUsers.js +111 -0
- package/css/themes/autumn.css +401 -0
- package/css/themes/bubblegum.css +408 -0
- package/css/themes/cashmere.css +412 -0
- package/css/themes/harvest.css +416 -0
- package/css/themes/moon.css +140 -0
- package/css/themes/ocean.css +273 -0
- package/css/themes/olive.css +413 -0
- package/css/themes/retro.css +431 -0
- package/css/themes/theme.css +725 -0
- package/error.vue +78 -0
- package/middleware/01.detect-subdomain.global.ts +43 -0
- package/middleware/02.validate-tenant.global.ts +67 -0
- package/middleware/03.apps.global.ts +88 -0
- package/middleware/auth.ts +9 -0
- package/middleware/guest.ts +9 -0
- package/nuxt.config.ts +42 -0
- package/package.json +60 -0
- package/pages/tenant-error.vue +50 -0
- package/plugins/api-auth.ts +12 -0
- package/plugins/api-tenant.client.ts +21 -0
- package/plugins/appearance.ts +8 -0
- package/plugins/auth-init.ts +34 -0
- package/plugins/dark-state.client.ts +29 -0
- package/plugins/dockedPreviewsSync.client.js +17 -0
- package/plugins/preline.client.ts +68 -0
- package/plugins/theme.client.ts +7 -0
- package/plugins/vue-query.ts +29 -0
- package/public/init-theme.js +15 -0
- package/spark.css +721 -0
- package/stores/auth.js +130 -0
- package/stores/dockedPreviews.js +34 -0
- package/stores/notifications.js +24 -0
- package/stores/tenant.js +54 -0
- package/stores/toast.js +129 -0
package/stores/auth.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
|
|
3
|
+
// ─── cookie helpers ────────────────────────────────────────────────────────
|
|
4
|
+
// Never call useCookie() inside Pinia actions — it throws outside Vue setup.
|
|
5
|
+
// Pattern: try useCookie (works in setup), catch → document.cookie fallback.
|
|
6
|
+
|
|
7
|
+
function _setCookie(name, value, maxAgeSeconds) {
|
|
8
|
+
try {
|
|
9
|
+
const isDev = import.meta.env?.DEV ?? false
|
|
10
|
+
const cookie = useCookie(name, { maxAge: maxAgeSeconds, sameSite: 'lax', secure: !isDev })
|
|
11
|
+
cookie.value = typeof value === 'object' ? JSON.stringify(value) : value
|
|
12
|
+
} catch {
|
|
13
|
+
if (import.meta.client) {
|
|
14
|
+
const expires = maxAgeSeconds ? `; Max-Age=${Math.floor(maxAgeSeconds)}` : ''
|
|
15
|
+
const val = typeof value === 'object' ? JSON.stringify(value) : value
|
|
16
|
+
document.cookie = `${name}=${encodeURIComponent(val)}${expires}; path=/; SameSite=Lax`
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function _getCookie(name) {
|
|
22
|
+
try {
|
|
23
|
+
const cookie = useCookie(name)
|
|
24
|
+
return cookie.value ?? null
|
|
25
|
+
} catch {
|
|
26
|
+
if (import.meta.client) {
|
|
27
|
+
const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'))
|
|
28
|
+
return match ? decodeURIComponent(match[1]) : null
|
|
29
|
+
}
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function _deleteCookie(name) {
|
|
35
|
+
try {
|
|
36
|
+
const cookie = useCookie(name)
|
|
37
|
+
cookie.value = null
|
|
38
|
+
} catch {
|
|
39
|
+
if (import.meta.client) {
|
|
40
|
+
document.cookie = `${name}=; Max-Age=0; path=/`
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── JWT decode (no lib) ───────────────────────────────────────────────────
|
|
46
|
+
function _decodeJwtExpiry(token) {
|
|
47
|
+
try {
|
|
48
|
+
const payload = JSON.parse(atob(token.split('.')[1]))
|
|
49
|
+
return payload.exp ?? null
|
|
50
|
+
} catch {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─── store ─────────────────────────────────────────────────────────────────
|
|
56
|
+
export const useAuthStore = defineStore('auth', {
|
|
57
|
+
state: () => ({
|
|
58
|
+
token: null,
|
|
59
|
+
user: null,
|
|
60
|
+
currentContext: null,
|
|
61
|
+
availableContexts: [],
|
|
62
|
+
permissions: [],
|
|
63
|
+
rememberUser: false,
|
|
64
|
+
}),
|
|
65
|
+
|
|
66
|
+
persist: [
|
|
67
|
+
{
|
|
68
|
+
// Token en cookie con key propio para que SSR lo lea en middlewares (guest/auth).
|
|
69
|
+
// Key separado para evitar que la segunda config sobreescriba el token.
|
|
70
|
+
key: 'auth_token',
|
|
71
|
+
pick: ['token'],
|
|
72
|
+
storage: piniaPluginPersistedstate.cookies,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
// currentContext y availableContexts en localStorage.
|
|
76
|
+
// user NO se persiste: auth-init.client.ts lo recarga desde API en cada boot,
|
|
77
|
+
// evitando que un user:null guardado en localStorage sobreescriba el estado.
|
|
78
|
+
key: 'auth_data',
|
|
79
|
+
pick: ['currentContext', 'availableContexts'],
|
|
80
|
+
storage: piniaPluginPersistedstate.localStorage,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
|
|
84
|
+
actions: {
|
|
85
|
+
// ── token ──────────────────────────────────────────────────────────────
|
|
86
|
+
saveToken(token) {
|
|
87
|
+
this.token = token
|
|
88
|
+
_setCookie('auth_token', token, 60 * 60 * 24 * 7) // 7 days
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
getToken() {
|
|
92
|
+
return this.token ?? _getCookie('auth_token')
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
isAuthenticated() {
|
|
96
|
+
const token = this.getToken()
|
|
97
|
+
if (!token) return false
|
|
98
|
+
const exp = _decodeJwtExpiry(token)
|
|
99
|
+
if (exp === null) return true // non-JWT or no expiry claim → treat as valid
|
|
100
|
+
return Date.now() / 1000 < exp
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// ── user ───────────────────────────────────────────────────────────────
|
|
104
|
+
saveUser(user) {
|
|
105
|
+
this.user = user
|
|
106
|
+
_setCookie('auth_user', user, 60 * 60 * 24 * 7)
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
// ── context ────────────────────────────────────────────────────────────
|
|
110
|
+
setCurrentContext(context) {
|
|
111
|
+
this.currentContext = context
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// ── permissions ────────────────────────────────────────────────────────
|
|
115
|
+
savePermissions(permissions) {
|
|
116
|
+
this.permissions = permissions ?? []
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
// ── logout ─────────────────────────────────────────────────────────────
|
|
120
|
+
logout() {
|
|
121
|
+
this.token = null
|
|
122
|
+
this.user = null
|
|
123
|
+
this.currentContext = null
|
|
124
|
+
this.availableContexts = []
|
|
125
|
+
this.permissions = []
|
|
126
|
+
_deleteCookie('auth_token')
|
|
127
|
+
_deleteCookie('auth_user')
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
|
|
3
|
+
// Storage SSR-safe: null en servidor, localStorage en cliente
|
|
4
|
+
const clientStorage = typeof window !== 'undefined' ? window.localStorage : null
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Store persistido en localStorage para los previews minimizados.
|
|
8
|
+
* Sobrevive recargas y se sincroniza entre pestañas (via plugin dockedPreviewsSync).
|
|
9
|
+
*/
|
|
10
|
+
export const useDockedPreviewsStore = defineStore('docked-previews', {
|
|
11
|
+
state: () => ({
|
|
12
|
+
items: [],
|
|
13
|
+
}),
|
|
14
|
+
|
|
15
|
+
actions: {
|
|
16
|
+
add({ id, label, subtitle, row, tableName, route }) {
|
|
17
|
+
if (this.items.find(d => d.id === id)) return
|
|
18
|
+
this.items.push({ id, label, subtitle: subtitle ?? null, row, tableName, route })
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
remove(id) {
|
|
22
|
+
this.items = this.items.filter(d => d.id !== id)
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
/** Sincroniza el estado desde otra pestaña (llamado por el plugin de storage). */
|
|
26
|
+
hydrate(items) {
|
|
27
|
+
this.items = items
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
persist: {
|
|
32
|
+
storage: clientStorage,
|
|
33
|
+
},
|
|
34
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
|
|
3
|
+
export const useNotificationsStore = defineStore('notifications', {
|
|
4
|
+
state: () => ({
|
|
5
|
+
notifications: [],
|
|
6
|
+
}),
|
|
7
|
+
getters: {
|
|
8
|
+
unreadCount: (state) => state.notifications.filter(n => !n.read_at).length,
|
|
9
|
+
},
|
|
10
|
+
actions: {
|
|
11
|
+
setNotifications(items) { this.notifications = items },
|
|
12
|
+
addNotification(item) { this.notifications.unshift(item) },
|
|
13
|
+
markRead(id) {
|
|
14
|
+
const n = this.notifications.find(n => n.id === id)
|
|
15
|
+
if (n) n.read_at = new Date().toISOString()
|
|
16
|
+
},
|
|
17
|
+
markAllRead() {
|
|
18
|
+
const now = new Date().toISOString()
|
|
19
|
+
this.notifications.forEach(n => { if (!n.read_at) n.read_at = now })
|
|
20
|
+
},
|
|
21
|
+
clear() { this.notifications = [] },
|
|
22
|
+
},
|
|
23
|
+
persist: false,
|
|
24
|
+
})
|
package/stores/tenant.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
|
|
3
|
+
export const useTenantStore = defineStore('tenant', {
|
|
4
|
+
state: () => ({
|
|
5
|
+
tenantId: null,
|
|
6
|
+
tenantSlug: null,
|
|
7
|
+
config: {
|
|
8
|
+
oauthProviders: [],
|
|
9
|
+
features: [],
|
|
10
|
+
isActive: false,
|
|
11
|
+
demo: null,
|
|
12
|
+
},
|
|
13
|
+
}),
|
|
14
|
+
|
|
15
|
+
persist: {
|
|
16
|
+
// tenantId y tenantSlug sobreviven el refresh; config siempre se recarga del backend
|
|
17
|
+
pick: ['tenantId', 'tenantSlug'],
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
actions: {
|
|
21
|
+
/** Llamado por el middleware detect-subdomain con el slug extraído */
|
|
22
|
+
setSlug(slug) {
|
|
23
|
+
this.tenantSlug = slug
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
/** Llamado tras la validación del backend — setea el id y la config completa */
|
|
27
|
+
setTenant(id, config) {
|
|
28
|
+
this.tenantId = id
|
|
29
|
+
this.config = {
|
|
30
|
+
oauthProviders: config.oauthProviders ?? [],
|
|
31
|
+
features: config.features ?? [],
|
|
32
|
+
isActive: config.isActive ?? false,
|
|
33
|
+
demo: config.demo ?? null,
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
/** Verifica si un feature flag está habilitado para este tenant */
|
|
38
|
+
isFeatureEnabled(feature) {
|
|
39
|
+
return this.config.features.includes(feature)
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
/** Devuelve los providers OAuth configurados para este tenant */
|
|
43
|
+
getOauthProviders() {
|
|
44
|
+
return this.config.oauthProviders ?? []
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/** Resetea todo el estado del tenant (ej. logout o tenant inválido) */
|
|
48
|
+
clear() {
|
|
49
|
+
this.tenantId = null
|
|
50
|
+
this.tenantSlug = null
|
|
51
|
+
this.config = { oauthProviders: [], features: [], isActive: false, demo: null }
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
})
|
package/stores/toast.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// stores/toast.js
|
|
2
|
+
import { defineStore } from 'pinia'
|
|
3
|
+
import { v4 as uuid } from 'uuid'
|
|
4
|
+
|
|
5
|
+
const toastConfig = {
|
|
6
|
+
severity: 'info',
|
|
7
|
+
title: null,
|
|
8
|
+
message: null,
|
|
9
|
+
duration: 5000,
|
|
10
|
+
progress: true,
|
|
11
|
+
position: 'top-right',
|
|
12
|
+
icon: null,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const useToastStore = defineStore('toast', {
|
|
16
|
+
state: () => ({
|
|
17
|
+
toasts: {
|
|
18
|
+
'top-left': [],
|
|
19
|
+
'top-center': [],
|
|
20
|
+
'top-right': [],
|
|
21
|
+
'bottom-left': [],
|
|
22
|
+
'bottom-center': [],
|
|
23
|
+
'bottom-right': [],
|
|
24
|
+
},
|
|
25
|
+
timeouts: {}, // id → timeoutId (plain object, JSON-safe)
|
|
26
|
+
}),
|
|
27
|
+
actions: {
|
|
28
|
+
success(config) {
|
|
29
|
+
const normalizedConfig = typeof config === 'string' ? { message: config } : config
|
|
30
|
+
const toast = {
|
|
31
|
+
...toastConfig,
|
|
32
|
+
id: uuid(),
|
|
33
|
+
severity: 'success',
|
|
34
|
+
icon: 'ti ti-circle-check text-green-500',
|
|
35
|
+
position: 'top-center',
|
|
36
|
+
...normalizedConfig,
|
|
37
|
+
}
|
|
38
|
+
this.toasts[toast.position].push(toast)
|
|
39
|
+
this._scheduleRemoval(toast)
|
|
40
|
+
},
|
|
41
|
+
info(config) {
|
|
42
|
+
const normalizedConfig = typeof config === 'string' ? { message: config } : config
|
|
43
|
+
const toast = {
|
|
44
|
+
...toastConfig,
|
|
45
|
+
id: uuid(),
|
|
46
|
+
severity: 'info',
|
|
47
|
+
icon: 'ti ti-info-circle',
|
|
48
|
+
title: 'Info',
|
|
49
|
+
position: 'top-right',
|
|
50
|
+
...normalizedConfig,
|
|
51
|
+
}
|
|
52
|
+
this.toasts[toast.position].push(toast)
|
|
53
|
+
this._scheduleRemoval(toast)
|
|
54
|
+
},
|
|
55
|
+
error(config) {
|
|
56
|
+
const normalizedConfig = typeof config === 'string' ? { message: config } : config
|
|
57
|
+
const toast = {
|
|
58
|
+
...toastConfig,
|
|
59
|
+
id: uuid(),
|
|
60
|
+
severity: 'danger',
|
|
61
|
+
icon: 'ti ti-exclamation-circle',
|
|
62
|
+
title: 'Atención',
|
|
63
|
+
position: 'top-right',
|
|
64
|
+
...normalizedConfig,
|
|
65
|
+
}
|
|
66
|
+
this.toasts[toast.position].push(toast)
|
|
67
|
+
this._scheduleRemoval(toast)
|
|
68
|
+
},
|
|
69
|
+
show(config) {
|
|
70
|
+
const toast = {
|
|
71
|
+
id: uuid(),
|
|
72
|
+
severity: 'info',
|
|
73
|
+
position: 'top-right',
|
|
74
|
+
...toastConfig,
|
|
75
|
+
...config,
|
|
76
|
+
}
|
|
77
|
+
this.toasts[toast.position].push(toast)
|
|
78
|
+
this._scheduleRemoval(toast)
|
|
79
|
+
},
|
|
80
|
+
update(id, data) {
|
|
81
|
+
for (const key in this.toasts) {
|
|
82
|
+
const idx = this.toasts[key].findIndex(t => t.id === id)
|
|
83
|
+
if (idx !== -1) {
|
|
84
|
+
this.toasts[key][idx] = { ...this.toasts[key][idx], ...data }
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
remove(id) {
|
|
90
|
+
if (this.timeouts[id] !== undefined) {
|
|
91
|
+
clearTimeout(this.timeouts[id])
|
|
92
|
+
delete this.timeouts[id]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const key in this.toasts) {
|
|
96
|
+
this.toasts[key] = this.toasts[key].filter(t => t.id !== id)
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
_scheduleRemoval(toast) {
|
|
100
|
+
if (toast.duration && toast.duration > 0) {
|
|
101
|
+
const timeoutId = setTimeout(() => {
|
|
102
|
+
this.remove(toast.id)
|
|
103
|
+
}, toast.duration)
|
|
104
|
+
|
|
105
|
+
this.timeouts[toast.id] = timeoutId
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
// Método específico para actualizar progreso de un toast tipo process
|
|
110
|
+
updateProgress(id, progress, progressLabel = null) {
|
|
111
|
+
const updateData = { progress }
|
|
112
|
+
if (progressLabel) {
|
|
113
|
+
updateData.progressLabel = progressLabel
|
|
114
|
+
}
|
|
115
|
+
this.update(id, updateData)
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// Método para completar un proceso
|
|
119
|
+
completeProcess(id, message = 'Completado') {
|
|
120
|
+
this.update(id, {
|
|
121
|
+
progress: 100,
|
|
122
|
+
progressLabel: message,
|
|
123
|
+
duration: 3000, // Auto-remove después de completar
|
|
124
|
+
})
|
|
125
|
+
// Programar removal para el proceso completado
|
|
126
|
+
this._scheduleRemoval({ id, duration: 3000 })
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
})
|