@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.
Files changed (108) hide show
  1. package/.github/workflows/auto-publish.yml +64 -0
  2. package/.github/workflows/release.yml +59 -0
  3. package/README.md +60 -0
  4. package/app.config.ts +70 -0
  5. package/components/Admin/Base.vue +144 -0
  6. package/components/Admin/Header.vue +32 -0
  7. package/components/Admin/Page.vue +65 -0
  8. package/components/Admin/PageHeader.vue +31 -0
  9. package/components/App/Button.vue +59 -0
  10. package/components/App/DevEnvironmentBar.vue +43 -0
  11. package/components/App/Dropdown.vue +286 -0
  12. package/components/App/EmptyState.vue +433 -0
  13. package/components/App/LoadingState.vue +40 -0
  14. package/components/App/PageLoadingSpinner.vue +118 -0
  15. package/components/App/PreviewDock.vue +64 -0
  16. package/components/App/SwitchColorTheme.vue +51 -0
  17. package/components/App/Tag.vue +193 -0
  18. package/components/DataTable.vue +713 -0
  19. package/components/Forms/DatePicker.vue +255 -0
  20. package/components/Forms/Input.vue +75 -0
  21. package/components/Forms/Select.vue +100 -0
  22. package/components/Forms/SelectServer.vue +726 -0
  23. package/components/Layout/Admin.vue +32 -0
  24. package/components/Layout/Auth.vue +29 -0
  25. package/components/Layout/SidebarWithAppColumn.vue +388 -0
  26. package/components/Layout/TopBar.vue +113 -0
  27. package/components/MobileBlocker.vue +85 -0
  28. package/components/MobileLoginPicker.vue +83 -0
  29. package/components/Modal/Base.vue +29 -0
  30. package/components/Modal/DeleteConfirm.vue +48 -0
  31. package/components/Modal.vue +103 -0
  32. package/components/Nav/Tabs.vue +55 -0
  33. package/components/PermissionsTree.vue +272 -0
  34. package/components/Table/Database.vue +183 -0
  35. package/components/Table/DownloadDropdown.vue +111 -0
  36. package/components/Table/Enterprise.vue +540 -0
  37. package/components/Table/FilterDropdown.vue +226 -0
  38. package/components/Table/Grid.vue +62 -0
  39. package/components/Table/Kanban.vue +188 -0
  40. package/components/Table/List.vue +128 -0
  41. package/components/Table/PreviewTimeline.vue +118 -0
  42. package/components/Table/Standard.vue +1217 -0
  43. package/components/Table/index.vue +974 -0
  44. package/components/TableExportable.vue +172 -0
  45. package/components/TableFilter.vue +93 -0
  46. package/components/Toast/Alert.vue +113 -0
  47. package/components/Toast/Container.vue +34 -0
  48. package/components/Toast/Notification.vue +45 -0
  49. package/components/Toast/Process.vue +88 -0
  50. package/composables/useApi.js +95 -0
  51. package/composables/useApp.ts +46 -0
  52. package/composables/useAuth.js +82 -0
  53. package/composables/useContext.js +44 -0
  54. package/composables/useDate.js +241 -0
  55. package/composables/useDevice.js +21 -0
  56. package/composables/useDockedPreviews.js +56 -0
  57. package/composables/useDownload.js +87 -0
  58. package/composables/useEntity.js +82 -0
  59. package/composables/useForm.js +119 -0
  60. package/composables/useInnertiaMode.ts +25 -0
  61. package/composables/useMobileGuard.ts +81 -0
  62. package/composables/useNotifications.js +22 -0
  63. package/composables/usePermissions.js +23 -0
  64. package/composables/useRealtime.js +123 -0
  65. package/composables/useRequestInterceptors.js +27 -0
  66. package/composables/useRoles.js +53 -0
  67. package/composables/useRutFormatter.js +39 -0
  68. package/composables/useTable.ts +94 -0
  69. package/composables/useTablePreferences.ts +33 -0
  70. package/composables/useTenant.js +27 -0
  71. package/composables/useTimeAgo.js +37 -0
  72. package/composables/useToast.js +69 -0
  73. package/composables/useUserRealtime.js +17 -0
  74. package/composables/useUsers.js +111 -0
  75. package/css/themes/autumn.css +401 -0
  76. package/css/themes/bubblegum.css +408 -0
  77. package/css/themes/cashmere.css +412 -0
  78. package/css/themes/harvest.css +416 -0
  79. package/css/themes/moon.css +140 -0
  80. package/css/themes/ocean.css +273 -0
  81. package/css/themes/olive.css +413 -0
  82. package/css/themes/retro.css +431 -0
  83. package/css/themes/theme.css +725 -0
  84. package/error.vue +78 -0
  85. package/middleware/01.detect-subdomain.global.ts +43 -0
  86. package/middleware/02.validate-tenant.global.ts +67 -0
  87. package/middleware/03.apps.global.ts +88 -0
  88. package/middleware/auth.ts +9 -0
  89. package/middleware/guest.ts +9 -0
  90. package/nuxt.config.ts +42 -0
  91. package/package.json +60 -0
  92. package/pages/tenant-error.vue +50 -0
  93. package/plugins/api-auth.ts +12 -0
  94. package/plugins/api-tenant.client.ts +21 -0
  95. package/plugins/appearance.ts +8 -0
  96. package/plugins/auth-init.ts +34 -0
  97. package/plugins/dark-state.client.ts +29 -0
  98. package/plugins/dockedPreviewsSync.client.js +17 -0
  99. package/plugins/preline.client.ts +68 -0
  100. package/plugins/theme.client.ts +7 -0
  101. package/plugins/vue-query.ts +29 -0
  102. package/public/init-theme.js +15 -0
  103. package/spark.css +721 -0
  104. package/stores/auth.js +130 -0
  105. package/stores/dockedPreviews.js +34 -0
  106. package/stores/notifications.js +24 -0
  107. package/stores/tenant.js +54 -0
  108. 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
+ })
@@ -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
+ })
@@ -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
+ })