create-nexgen 1.0.4

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 (240) hide show
  1. package/package.json +26 -0
  2. package/src/index.js +108 -0
  3. package/template/.dockerignore +14 -0
  4. package/template/.env +58 -0
  5. package/template/.env.example +59 -0
  6. package/template/.prettierignore +5 -0
  7. package/template/.prettierrc +8 -0
  8. package/template/README.md +447 -0
  9. package/template/drizzle.config.ts +29 -0
  10. package/template/eslint.config.js +52 -0
  11. package/template/gitignore-stub +24 -0
  12. package/template/package.json +96 -0
  13. package/template/public/assets/AuthLayout-CbswhpjJ.js +1 -0
  14. package/template/public/assets/Button-_7aQ7gHL.js +1 -0
  15. package/template/public/assets/Input-CLNJXmKc.css +1 -0
  16. package/template/public/assets/Input-z8GI8Aqo.js +1 -0
  17. package/template/public/assets/InputPasswordToggle-BxlzVGp3.js +1 -0
  18. package/template/public/assets/InputPasswordToggle-C77FI9Eg.css +1 -0
  19. package/template/public/assets/Layout-DotR1sQC.js +1 -0
  20. package/template/public/assets/Refresh-BdqsPPBC.js +1 -0
  21. package/template/public/assets/admin-ui-CU34rLdN.js +1 -0
  22. package/template/public/assets/bootstrap-icons-BeopsB42.woff +0 -0
  23. package/template/public/assets/bootstrap-icons-mSm7cUeB.woff2 +0 -0
  24. package/template/public/assets/dashboard-CwybEyLc.js +1 -0
  25. package/template/public/assets/dashboard-Dc4d-Pi7.css +1 -0
  26. package/template/public/assets/forgetPassword-CKEJaXsq.js +1 -0
  27. package/template/public/assets/index-Bleyx5dm.js +64 -0
  28. package/template/public/assets/index-DUw8E6Yg.css +1 -0
  29. package/template/public/assets/login-DC7PTlQF.js +1 -0
  30. package/template/public/assets/realtime-test-BPQdrFym.css +1 -0
  31. package/template/public/assets/realtime-test-tQZ0rBEJ.js +1 -0
  32. package/template/public/assets/register-3O7Qs28C.js +1 -0
  33. package/template/public/assets/resetPassword-A5AzMWKs.js +1 -0
  34. package/template/public/assets/verifyEmail-DDBEQHOv.js +1 -0
  35. package/template/public/index.html +17 -0
  36. package/template/src/database/migrations/mysql/0000_init.sql +73 -0
  37. package/template/src/database/migrations/mysql/meta/0000_snapshot.json +484 -0
  38. package/template/src/database/migrations/mysql/meta/_journal.json +13 -0
  39. package/template/src/database/schema.ts +4 -0
  40. package/template/src/env.ts +107 -0
  41. package/template/src/framework/cache/cache.ts +81 -0
  42. package/template/src/framework/database/connection.ts +168 -0
  43. package/template/src/framework/database/optional-db-drivers.d.ts +9 -0
  44. package/template/src/framework/database/paginate.ts +200 -0
  45. package/template/src/framework/database/schema.ts +26 -0
  46. package/template/src/framework/database/seed.ts +33 -0
  47. package/template/src/framework/events/dispatcher.ts +57 -0
  48. package/template/src/framework/facade.ts +27 -0
  49. package/template/src/framework/http/app.ts +61 -0
  50. package/template/src/framework/http/cors.ts +19 -0
  51. package/template/src/framework/http/logger.ts +85 -0
  52. package/template/src/framework/http/openapi.ts +34 -0
  53. package/template/src/framework/http/ratelimiter.ts +13 -0
  54. package/template/src/framework/http/router.ts +76 -0
  55. package/template/src/framework/http/static.ts +33 -0
  56. package/template/src/framework/http/validation.ts +24 -0
  57. package/template/src/framework/kernel.ts +40 -0
  58. package/template/src/framework/maker-cli/src/index.mjs +51 -0
  59. package/template/src/framework/maker-cli/src/levels/level-1/env-db.mjs +57 -0
  60. package/template/src/framework/maker-cli/src/levels/level-1/file-ops.mjs +30 -0
  61. package/template/src/framework/maker-cli/src/levels/level-1/flags.mjs +16 -0
  62. package/template/src/framework/maker-cli/src/levels/level-1/help.mjs +24 -0
  63. package/template/src/framework/maker-cli/src/levels/level-1/naming.mjs +13 -0
  64. package/template/src/framework/maker-cli/src/levels/level-1/process.mjs +47 -0
  65. package/template/src/framework/maker-cli/src/levels/level-2/db/core.mjs +299 -0
  66. package/template/src/framework/maker-cli/src/levels/level-2/db/index.mjs +177 -0
  67. package/template/src/framework/maker-cli/src/levels/level-2/deploy/core.mjs +635 -0
  68. package/template/src/framework/maker-cli/src/levels/level-2/deploy/index.mjs +145 -0
  69. package/template/src/framework/maker-cli/src/levels/level-2/module/core.mjs +707 -0
  70. package/template/src/framework/maker-cli/src/levels/level-2/module/index.mjs +116 -0
  71. package/template/src/framework/maker-cli/src/levels/level-2/runtime/build-frontend.mjs +16 -0
  72. package/template/src/framework/maker-cli/src/levels/level-2/runtime/core.mjs +311 -0
  73. package/template/src/framework/maker-cli/src/levels/level-2/runtime/index.mjs +71 -0
  74. package/template/src/framework/maker-cli/stubs/controller/openapi.ts.stub +55 -0
  75. package/template/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +56 -0
  76. package/template/src/framework/maker-cli/stubs/controller/plain.ts.stub +57 -0
  77. package/template/src/framework/maker-cli/stubs/controller/schema.plain.ts.stub +13 -0
  78. package/template/src/framework/maker-cli/stubs/controller/schema.ts.stub +32 -0
  79. package/template/src/framework/maker-cli/stubs/deploy/Dockerfile.bun.stub +49 -0
  80. package/template/src/framework/maker-cli/stubs/deploy/Dockerfile.pnpm.stub +53 -0
  81. package/template/src/framework/maker-cli/stubs/deploy/Dockerfile.stub +49 -0
  82. package/template/src/framework/maker-cli/stubs/deploy/Dockerfile.yarn.stub +53 -0
  83. package/template/src/framework/maker-cli/stubs/deploy/README.stub +55 -0
  84. package/template/src/framework/maker-cli/stubs/deploy/compose/mysql.server.stub +29 -0
  85. package/template/src/framework/maker-cli/stubs/deploy/compose/postgres.server.stub +29 -0
  86. package/template/src/framework/maker-cli/stubs/deploy/compose/sqlite.stub +29 -0
  87. package/template/src/framework/maker-cli/stubs/deploy/env/mysql.server.stub +73 -0
  88. package/template/src/framework/maker-cli/stubs/deploy/env/postgres.server.stub +73 -0
  89. package/template/src/framework/maker-cli/stubs/deploy/env/sqlite.stub +72 -0
  90. package/template/src/framework/maker-cli/stubs/deploy/scripts/auto-migrate.sh.stub +15 -0
  91. package/template/src/framework/maker-cli/stubs/deploy/server/README.stub +77 -0
  92. package/template/src/framework/maker-cli/stubs/deploy/server/compose/noredis.stub +118 -0
  93. package/template/src/framework/maker-cli/stubs/deploy/server/compose/redis.dev.stub +131 -0
  94. package/template/src/framework/maker-cli/stubs/deploy/server/compose/redis.stub +129 -0
  95. package/template/src/framework/maker-cli/stubs/deploy/server/env/local.example.stub +10 -0
  96. package/template/src/framework/maker-cli/stubs/deploy/server/env/noredis.stub +24 -0
  97. package/template/src/framework/maker-cli/stubs/deploy/server/env/redis.stub +24 -0
  98. package/template/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/README.stub +15 -0
  99. package/template/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/app.example.com.stub +12 -0
  100. package/template/src/framework/maker-cli/stubs/deploy/server/pgadmin/servers.stub +13 -0
  101. package/template/src/framework/maker-cli/stubs/deploy/server/redis/redis.conf.stub +6 -0
  102. package/template/src/framework/maker-cli/stubs/deploy/supervisor/noredis.stub +53 -0
  103. package/template/src/framework/maker-cli/stubs/deploy/supervisor/redis.stub +69 -0
  104. package/template/src/framework/maker-cli/stubs/deploy/workflow/local.json.stub +24 -0
  105. package/template/src/framework/maker-cli/stubs/deploy/workflow/remote.json.stub +20 -0
  106. package/template/src/framework/maker-cli/stubs/example/console.ts.stub +33 -0
  107. package/template/src/framework/maker-cli/stubs/example/controller.ts.stub +503 -0
  108. package/template/src/framework/maker-cli/stubs/example/job.ts.stub +74 -0
  109. package/template/src/framework/maker-cli/stubs/example/route.api.ts.stub +206 -0
  110. package/template/src/framework/maker-cli/stubs/example/schema.ts.stub +41 -0
  111. package/template/src/framework/maker-cli/stubs/job/name.ts.stub +24 -0
  112. package/template/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +8 -0
  113. package/template/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +8 -0
  114. package/template/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +8 -0
  115. package/template/src/framework/maker-cli/stubs/notification/NotificationBell.vue.stub +218 -0
  116. package/template/src/framework/maker-cli/stubs/notification/controller.ts.stub +85 -0
  117. package/template/src/framework/maker-cli/stubs/notification/index.vue.stub +211 -0
  118. package/template/src/framework/maker-cli/stubs/notification/job.ts.stub +12 -0
  119. package/template/src/framework/maker-cli/stubs/notification/route.api.ts.stub +49 -0
  120. package/template/src/framework/maker-cli/stubs/notification/schema.ts.stub +25 -0
  121. package/template/src/framework/maker-cli/stubs/route/api.ts.stub +79 -0
  122. package/template/src/framework/maker-cli/stubs/route/plain.ts.stub +10 -0
  123. package/template/src/framework/maker-cli/stubs/schedule/name.ts.stub +35 -0
  124. package/template/src/framework/maker-cli/stubs/seeder/name.ts.stub +17 -0
  125. package/template/src/framework/modules/discover.ts +54 -0
  126. package/template/src/framework/modules/routes.ts +26 -0
  127. package/template/src/framework/notification/index.ts +109 -0
  128. package/template/src/framework/queue/clear.ts +20 -0
  129. package/template/src/framework/queue/queue.ts +213 -0
  130. package/template/src/framework/queue/ui.ts +104 -0
  131. package/template/src/framework/queue/worker.ts +33 -0
  132. package/template/src/framework/realtime/broadcast.ts +27 -0
  133. package/template/src/framework/realtime/index.ts +1 -0
  134. package/template/src/framework/realtime/socket-cookie.ts +65 -0
  135. package/template/src/framework/realtime/socket.ts +132 -0
  136. package/template/src/framework/realtime/types.ts +6 -0
  137. package/template/src/framework/realtime/ui.ts +16 -0
  138. package/template/src/framework/redis/client.ts +126 -0
  139. package/template/src/framework/scheduler/lock.ts +124 -0
  140. package/template/src/framework/scheduler/run.ts +26 -0
  141. package/template/src/framework/scheduler/scheduler.ts +82 -0
  142. package/template/src/framework/server.ts +147 -0
  143. package/template/src/framework/session/session.ts +116 -0
  144. package/template/src/framework/storage/storage.ts +743 -0
  145. package/template/src/framework/support/cookie.ts +78 -0
  146. package/template/src/framework/support/jwt.ts +45 -0
  147. package/template/src/framework/support/lifecycle.ts +35 -0
  148. package/template/src/framework/support/logger.ts +102 -0
  149. package/template/src/framework/support/mail.ts +43 -0
  150. package/template/src/framework/support/password.ts +23 -0
  151. package/template/src/framework/support/url.ts +25 -0
  152. package/template/src/middlewares/auth-middleware.ts +98 -0
  153. package/template/src/middlewares/role-middleware.ts +24 -0
  154. package/template/src/modules/auth/controllers/auth.controller.ts +445 -0
  155. package/template/src/modules/auth/controllers/auth.helpers.ts +110 -0
  156. package/template/src/modules/auth/controllers/auth.schema.ts +102 -0
  157. package/template/src/modules/auth/controllers/role.controller.ts +25 -0
  158. package/template/src/modules/auth/database/models/notifications.ts +22 -0
  159. package/template/src/modules/auth/database/models/role.ts +14 -0
  160. package/template/src/modules/auth/database/models/user.ts +46 -0
  161. package/template/src/modules/auth/database/seeders/role.ts +19 -0
  162. package/template/src/modules/auth/database/seeders/user.ts +33 -0
  163. package/template/src/modules/auth/jobs/forgetpass.ts +18 -0
  164. package/template/src/modules/auth/jobs/registeruser.ts +31 -0
  165. package/template/src/modules/auth/jobs/verifyemail.ts +18 -0
  166. package/template/src/modules/auth/routes/api.ts +151 -0
  167. package/template/src/modules/auth/routes/role.ts +39 -0
  168. package/template/src/modules/welcome/controllers/welcome.controller.ts +14 -0
  169. package/template/src/modules/welcome/controllers/welcome.schema.ts +6 -0
  170. package/template/src/modules/welcome/database/models/welcome.ts +6 -0
  171. package/template/src/modules/welcome/routes/api.ts +20 -0
  172. package/template/src/resources/index.html +16 -0
  173. package/template/src/resources/src/App.vue +5 -0
  174. package/template/src/resources/src/assets/css/styles.css +14934 -0
  175. package/template/src/resources/src/assets/css/styles.css.map +1 -0
  176. package/template/src/resources/src/assets/images/favicon/favicon.ico +0 -0
  177. package/template/src/resources/src/assets/images/favicon/favicon1.ico +0 -0
  178. package/template/src/resources/src/assets/images/logo-1.png +0 -0
  179. package/template/src/resources/src/assets/images/logo-dark-sm.png +0 -0
  180. package/template/src/resources/src/assets/images/logo-dark.png +0 -0
  181. package/template/src/resources/src/assets/images/logo-dark1.png +0 -0
  182. package/template/src/resources/src/assets/images/logo-sm.png +0 -0
  183. package/template/src/resources/src/assets/images/logo1.png +0 -0
  184. package/template/src/resources/src/assets/images/logo2.png +0 -0
  185. package/template/src/resources/src/assets/scss/custom.css +217 -0
  186. package/template/src/resources/src/assets/scss/custom.css.map +1 -0
  187. package/template/src/resources/src/assets/scss/custom.scss +1100 -0
  188. package/template/src/resources/src/components/Button.vue +35 -0
  189. package/template/src/resources/src/components/Checkbox.vue +29 -0
  190. package/template/src/resources/src/components/FloatButton.vue +36 -0
  191. package/template/src/resources/src/components/Href.vue +32 -0
  192. package/template/src/resources/src/components/Input.vue +227 -0
  193. package/template/src/resources/src/components/InputGroup.vue +153 -0
  194. package/template/src/resources/src/components/InputPasswordToggle.vue +226 -0
  195. package/template/src/resources/src/components/Modal.vue +102 -0
  196. package/template/src/resources/src/components/Pagebar.vue +28 -0
  197. package/template/src/resources/src/components/Refresh.vue +26 -0
  198. package/template/src/resources/src/components/Select.vue +390 -0
  199. package/template/src/resources/src/components/Spinner.vue +42 -0
  200. package/template/src/resources/src/components/Switch.vue +65 -0
  201. package/template/src/resources/src/components/TextArea.vue +121 -0
  202. package/template/src/resources/src/components/Toast.vue +56 -0
  203. package/template/src/resources/src/components/datatable/DataTableSkeleton.vue +99 -0
  204. package/template/src/resources/src/components/datatable/Pagination.vue +161 -0
  205. package/template/src/resources/src/components/datatable/SelectOpption.vue +54 -0
  206. package/template/src/resources/src/components/datatable/index.vue +237 -0
  207. package/template/src/resources/src/composables/useAuth.ts +52 -0
  208. package/template/src/resources/src/composables/useBrowserDetect.ts +5 -0
  209. package/template/src/resources/src/composables/useDialog.ts +5 -0
  210. package/template/src/resources/src/composables/useGum.ts +3 -0
  211. package/template/src/resources/src/composables/usePulse.ts +5 -0
  212. package/template/src/resources/src/env.d.ts +20 -0
  213. package/template/src/resources/src/helpers/nformatter.ts +10 -0
  214. package/template/src/resources/src/helpers/utils.ts +68 -0
  215. package/template/src/resources/src/layouts/AuthLayout.vue +20 -0
  216. package/template/src/resources/src/layouts/Layout/Footer.vue +23 -0
  217. package/template/src/resources/src/layouts/Layout/Header.vue +90 -0
  218. package/template/src/resources/src/layouts/Layout/Sidebar.vue +137 -0
  219. package/template/src/resources/src/layouts/Layout/index.vue +76 -0
  220. package/template/src/resources/src/main.ts +27 -0
  221. package/template/src/resources/src/pages/auth/forgetPassword.vue +76 -0
  222. package/template/src/resources/src/pages/auth/login.vue +93 -0
  223. package/template/src/resources/src/pages/auth/register.vue +130 -0
  224. package/template/src/resources/src/pages/auth/resetPassword.vue +119 -0
  225. package/template/src/resources/src/pages/auth/verifyEmail.vue +60 -0
  226. package/template/src/resources/src/pages/dashboard/index.vue +76 -0
  227. package/template/src/resources/src/plugins/axios.ts +33 -0
  228. package/template/src/resources/src/plugins/browserDetect.ts +55 -0
  229. package/template/src/resources/src/plugins/dialog.ts +167 -0
  230. package/template/src/resources/src/plugins/gum.ts +343 -0
  231. package/template/src/resources/src/plugins/pulse.ts +141 -0
  232. package/template/src/resources/src/plugins/routeProgress.ts +87 -0
  233. package/template/src/resources/src/router/index.ts +85 -0
  234. package/template/src/resources/src/stores/admin-ui.ts +148 -0
  235. package/template/src/resources/src/stores/auth.ts +151 -0
  236. package/template/src/resources/tsconfig.json +19 -0
  237. package/template/src/resources/vite.config.ts +43 -0
  238. package/template/src/storage/logs/app.log +20179 -0
  239. package/template/src/storage/logs/fatal.log +727 -0
  240. package/template/tsconfig.json +20 -0
@@ -0,0 +1,343 @@
1
+ import type { App } from "vue";
2
+ import { computed, reactive, ref, toRaw } from "vue";
3
+ import { useStorage } from "@vueuse/core";
4
+ import { useRoute, useRouter, type LocationQueryRaw } from "vue-router";
5
+ import axios, { type AxiosError, type AxiosProgressEvent, type AxiosResponse } from "axios";
6
+
7
+ export type GumPluginOptions = {
8
+ rememberPrefix?: string;
9
+ recentlySuccessfulDuration?: number;
10
+ };
11
+
12
+ type GumMethod = "get" | "post" | "put" | "patch" | "delete";
13
+ type GumVisitOptions = {
14
+ method?: GumMethod;
15
+ data?: Record<string, unknown> | FormData;
16
+ query?: Record<string, unknown>;
17
+ replace?: boolean;
18
+ preserveState?: boolean;
19
+ preserveScroll?: boolean;
20
+ onBefore?: () => boolean | void;
21
+ onStart?: () => void;
22
+ onProgress?: (event: AxiosProgressEvent) => void;
23
+ onSuccess?: (response: AxiosResponse) => void;
24
+ onError?: (error: AxiosError) => void;
25
+ onFinish?: () => void;
26
+ };
27
+
28
+ type FormMethod = "post" | "put" | "patch" | "delete";
29
+ type FormErrors<T> = Partial<Record<keyof T | string, string>>;
30
+ type FormSubmitOptions = {
31
+ onStart?: () => void;
32
+ onSuccess?: () => void;
33
+ onError?: (error: AxiosError) => void;
34
+ onFinish?: () => void;
35
+ };
36
+
37
+ const config: Required<GumPluginOptions> = {
38
+ rememberPrefix: "gum",
39
+ recentlySuccessfulDuration: 2000
40
+ };
41
+
42
+ /**
43
+ * Why: preserveState=false should clear only state for one page.
44
+ * When: called by remember registration and GET visits.
45
+ * Where: used in this Gum plugin storage registry.
46
+ */
47
+ const routeRememberKeys = new Map<string, Set<string>>();
48
+
49
+ /**
50
+ * Why: route keys must be consistent across query changes.
51
+ * When: before read/write in remember key registry.
52
+ * Where: internal helper for Gum path-based state tracking.
53
+ */
54
+ function normalizePath(path: string) {
55
+ const clean = (path || "/").split("?")[0];
56
+ return clean || "/";
57
+ }
58
+
59
+ function registerRememberKey(path: string, key: string) {
60
+ const routePath = normalizePath(path);
61
+ if (!routeRememberKeys.has(routePath)) routeRememberKeys.set(routePath, new Set<string>());
62
+ routeRememberKeys.get(routePath)?.add(key);
63
+ }
64
+
65
+ /**
66
+ * Why: emulate Inertia preserveState=false behavior.
67
+ * When: a GET visit requests state reset.
68
+ * Where: removes entries from localStorage using Gum prefix.
69
+ */
70
+ export function clearRememberForPath(path: string) {
71
+ const routePath = normalizePath(path);
72
+ const keys = routeRememberKeys.get(routePath);
73
+ if (!keys) return;
74
+ keys.forEach((key) => localStorage.removeItem(`${config.rememberPrefix}:${key}`));
75
+ }
76
+
77
+ /**
78
+ * Why: persist page-local UI state across navigations/reloads.
79
+ * When: page needs remembered filters/forms.
80
+ * Where: composables/pages calling useGumRemember.
81
+ */
82
+ export function useGumRemember<T extends object>(key: string, initial: T) {
83
+ const route = useRoute();
84
+ registerRememberKey(route.path, key);
85
+
86
+ return useStorage<T>(`${config.rememberPrefix}:${key}`, initial, localStorage, {
87
+ mergeDefaults: true
88
+ });
89
+ }
90
+
91
+ /**
92
+ * Why: provide Inertia-style visit API for requests + navigation.
93
+ * When: pages trigger get/post/put/patch/delete/reload flows.
94
+ * Where: frontend pages/composables that import useGum.
95
+ */
96
+ export function useGum() {
97
+ const router = useRouter();
98
+ const route = useRoute();
99
+ const processing = ref(false);
100
+
101
+ /**
102
+ * Why: unify request lifecycle hooks with router sync.
103
+ * When: any Gum visit method is executed.
104
+ * Where: internal core of useGum().
105
+ */
106
+ async function visit(url: string, options: GumVisitOptions = {}) {
107
+ const {
108
+ method = "get",
109
+ data,
110
+ query,
111
+ replace = false,
112
+ preserveState = method !== "get",
113
+ preserveScroll = false,
114
+ onBefore,
115
+ onStart,
116
+ onProgress,
117
+ onSuccess,
118
+ onError,
119
+ onFinish
120
+ } = options;
121
+
122
+ const allow = onBefore?.();
123
+ if (allow === false) return;
124
+
125
+ const scrollY = window.scrollY;
126
+ processing.value = true;
127
+ onStart?.();
128
+
129
+ try {
130
+ const response = await axios.request({
131
+ method,
132
+ url,
133
+ params: method === "get" ? (query ?? (data as Record<string, unknown> | undefined)) : query,
134
+ data: method === "get" ? undefined : data,
135
+ onUploadProgress: (event) => onProgress?.(event)
136
+ });
137
+
138
+ if (method === "get") {
139
+ if (!preserveState) clearRememberForPath(url);
140
+
141
+ const payload = {
142
+ path: url,
143
+ query: (query ?? route.query) as LocationQueryRaw
144
+ };
145
+
146
+ if (replace) await router.replace(payload);
147
+ else await router.push(payload);
148
+ }
149
+
150
+ onSuccess?.(response);
151
+ return response;
152
+ } catch (error) {
153
+ onError?.(error as AxiosError);
154
+ throw error;
155
+ } finally {
156
+ processing.value = false;
157
+ onFinish?.();
158
+ if (preserveScroll) window.scrollTo({ top: scrollY });
159
+ }
160
+ }
161
+
162
+ return {
163
+ processing,
164
+ visit,
165
+ get: (url: string, options: Omit<GumVisitOptions, "method"> = {}) =>
166
+ visit(url, { ...options, method: "get" }),
167
+ post: (
168
+ url: string,
169
+ data?: Record<string, unknown> | FormData,
170
+ options: Omit<GumVisitOptions, "method" | "data"> = {}
171
+ ) => {
172
+ return visit(url, { ...options, method: "post", data });
173
+ },
174
+ put: (
175
+ url: string,
176
+ data?: Record<string, unknown> | FormData,
177
+ options: Omit<GumVisitOptions, "method" | "data"> = {}
178
+ ) => {
179
+ return visit(url, { ...options, method: "put", data });
180
+ },
181
+ patch: (
182
+ url: string,
183
+ data?: Record<string, unknown> | FormData,
184
+ options: Omit<GumVisitOptions, "method" | "data"> = {}
185
+ ) => {
186
+ return visit(url, { ...options, method: "patch", data });
187
+ },
188
+ delete: (url: string, options: Omit<GumVisitOptions, "method"> = {}) =>
189
+ visit(url, { ...options, method: "delete" }),
190
+ reload: (options: Omit<GumVisitOptions, "method"> = {}) => {
191
+ return visit(route.path, {
192
+ ...options,
193
+ method: "get",
194
+ replace: true,
195
+ query: route.query as Record<string, unknown>
196
+ });
197
+ }
198
+ };
199
+ }
200
+
201
+ /**
202
+ * Why: centralize form state/errors/progress like Inertia useForm.
203
+ * When: create/update/delete forms submit to backend.
204
+ * Where: frontend forms using useGumForm.
205
+ */
206
+ export function useGumForm<T extends Record<string, unknown>>(defaults: T) {
207
+ const initial = structuredClone(defaults);
208
+ const data = reactive(structuredClone(defaults)) as T;
209
+ const errors = reactive<Record<string, string>>({});
210
+ const progress = ref<number | null>(null);
211
+ const processing = ref(false);
212
+ const wasSuccessful = ref(false);
213
+ const recentlySuccessful = ref(false);
214
+ const isDirty = computed(() => JSON.stringify(toRaw(data)) !== JSON.stringify(initial));
215
+
216
+ /**
217
+ * Why: allow manual error assignment for custom validations.
218
+ * When: setting one field error outside server response.
219
+ * Where: useGumForm consumer code.
220
+ */
221
+ function setError(field: keyof T | string, message: string) {
222
+ errors[String(field)] = message;
223
+ }
224
+
225
+ /**
226
+ * Why: keep error state in sync with user actions/submits.
227
+ * When: before submit or after field corrections.
228
+ * Where: useGumForm internal + consumer calls.
229
+ */
230
+ function clearErrors(...fields: (keyof T | string)[]) {
231
+ if (!fields.length) {
232
+ Object.keys(errors).forEach((key) => delete errors[key]);
233
+ return;
234
+ }
235
+ fields.forEach((field) => delete errors[String(field)]);
236
+ }
237
+
238
+ /**
239
+ * Why: restore form values to initial defaults safely.
240
+ * When: cancel edit or after successful submission.
241
+ * Where: useGumForm consumer actions.
242
+ */
243
+ function reset(...fields: (keyof T)[]) {
244
+ if (!fields.length) {
245
+ Object.assign(data, structuredClone(initial));
246
+ clearErrors();
247
+ return;
248
+ }
249
+
250
+ fields.forEach((field) => {
251
+ data[field] = structuredClone(initial[field]);
252
+ delete errors[String(field)];
253
+ });
254
+ }
255
+
256
+ /**
257
+ * Why: provide a single submission path with lifecycle hooks.
258
+ * When: post/put/patch/delete helpers are called.
259
+ * Where: useGumForm internal request executor.
260
+ */
261
+ async function submit(
262
+ method: FormMethod,
263
+ url: string,
264
+ payload?: Record<string, unknown>,
265
+ options: FormSubmitOptions = {}
266
+ ) {
267
+ const { onStart, onSuccess, onError, onFinish } = options;
268
+ wasSuccessful.value = false;
269
+ clearErrors();
270
+ processing.value = true;
271
+ onStart?.();
272
+
273
+ try {
274
+ const response = await axios.request({
275
+ method,
276
+ url,
277
+ data: payload ?? toRaw(data),
278
+ onUploadProgress: (event) => {
279
+ if (!event.total) return;
280
+ progress.value = Math.round((event.loaded * 100) / event.total);
281
+ }
282
+ });
283
+
284
+ wasSuccessful.value = true;
285
+ recentlySuccessful.value = true;
286
+ setTimeout(() => {
287
+ recentlySuccessful.value = false;
288
+ }, config.recentlySuccessfulDuration);
289
+ onSuccess?.();
290
+ return response;
291
+ } catch (error) {
292
+ const err = error as AxiosError<{ errors?: Record<string, string[] | string> }>;
293
+ const serverErrors = err.response?.data?.errors;
294
+ if (serverErrors) {
295
+ Object.entries(serverErrors).forEach(([key, value]) => {
296
+ errors[String(key)] = Array.isArray(value) ? value[0] : value;
297
+ });
298
+ }
299
+ onError?.(error as AxiosError);
300
+ throw error;
301
+ } finally {
302
+ processing.value = false;
303
+ progress.value = null;
304
+ onFinish?.();
305
+ }
306
+ }
307
+
308
+ return {
309
+ data,
310
+ errors: errors as FormErrors<T>,
311
+ progress,
312
+ processing,
313
+ wasSuccessful,
314
+ recentlySuccessful,
315
+ isDirty,
316
+ setError,
317
+ clearErrors,
318
+ reset,
319
+ submit,
320
+ post: (url: string, payload?: Record<string, unknown>, options?: FormSubmitOptions) =>
321
+ submit("post", url, payload, options),
322
+ put: (url: string, payload?: Record<string, unknown>, options?: FormSubmitOptions) =>
323
+ submit("put", url, payload, options),
324
+ patch: (url: string, payload?: Record<string, unknown>, options?: FormSubmitOptions) =>
325
+ submit("patch", url, payload, options),
326
+ delete: (url: string, payload?: Record<string, unknown>, options?: FormSubmitOptions) =>
327
+ submit("delete", url, payload, options)
328
+ };
329
+ }
330
+
331
+ /**
332
+ * Why: configure shared Gum behavior globally.
333
+ * When: app bootstrap calls app.use(GumPlugin, options).
334
+ * Where: src/resources/src/main.ts plugin registration.
335
+ */
336
+ export const GumPlugin = {
337
+ install(_app: App, options: GumPluginOptions = {}) {
338
+ if (options.rememberPrefix) config.rememberPrefix = options.rememberPrefix;
339
+ if (typeof options.recentlySuccessfulDuration === "number") {
340
+ config.recentlySuccessfulDuration = options.recentlySuccessfulDuration;
341
+ }
342
+ }
343
+ };
@@ -0,0 +1,141 @@
1
+ import type { App } from "vue";
2
+ import { io, type Socket } from "socket.io-client";
3
+
4
+ declare const __SOCKET_ENABLED__: boolean;
5
+
6
+ type EventCallback<T = unknown> = (payload: T) => void;
7
+
8
+ type ChannelBinding = {
9
+ event: string;
10
+ callback: EventCallback;
11
+ wrapped: EventCallback;
12
+ };
13
+
14
+ type PulseChannel = {
15
+ listen: <T = unknown>(event: string, callback: EventCallback<T>) => PulseChannel;
16
+ stopListening: (event: string, callback?: EventCallback) => PulseChannel;
17
+ };
18
+
19
+ export type PulseClient = {
20
+ connect: () => PulseClient;
21
+ disconnect: () => PulseClient;
22
+ channel: (name: string) => PulseChannel;
23
+ private: (room: string) => PulseChannel;
24
+ leave: (room: string) => PulseClient;
25
+ };
26
+
27
+ function createPulse(): PulseClient {
28
+ if (!__SOCKET_ENABLED__) {
29
+ const n = (): PulseChannel => ({ listen: () => n(), stopListening: () => n() });
30
+ const c: PulseClient = { connect: () => c, disconnect: () => c, channel: n, private: n, leave: () => c };
31
+ return c;
32
+ }
33
+ let socket: Socket | null = null;
34
+ const channels = new Map<string, ChannelBinding[]>();
35
+ const joinedRooms = new Set<string>();
36
+
37
+ const socketUrl = () => {
38
+ const explicit = import.meta.env.VITE_SOCKET_URL || import.meta.env.VITE_API_URL;
39
+ if (explicit) return explicit;
40
+ return window.location.origin;
41
+ };
42
+
43
+ const ensureConnected = () => {
44
+ if (socket) return socket;
45
+
46
+ socket = io(socketUrl(), {
47
+ withCredentials: true,
48
+ transports: ["websocket", "polling"]
49
+ });
50
+
51
+ socket.on("connect", () => {
52
+ for (const room of joinedRooms) {
53
+ socket?.emit("join", room);
54
+ }
55
+ });
56
+
57
+ return socket;
58
+ };
59
+
60
+ const channel = (name: string): PulseChannel => {
61
+ const room = String(name).trim();
62
+ if (!room) throw new Error("Channel name is required");
63
+
64
+ const currentSocket = ensureConnected();
65
+ joinedRooms.add(room);
66
+ currentSocket.emit("join", room);
67
+
68
+ const api: PulseChannel = {
69
+ listen: <T = unknown>(event: string, callback: EventCallback<T>) => {
70
+ const wrapped = (payload: unknown) => callback(payload as T);
71
+ const bindings = channels.get(room) || [];
72
+ bindings.push({ event, callback: callback as EventCallback, wrapped });
73
+ channels.set(room, bindings);
74
+ currentSocket.on(event, wrapped);
75
+ return api;
76
+ },
77
+ stopListening: (event: string, callback?: EventCallback) => {
78
+ const bindings = channels.get(room) || [];
79
+ const kept: ChannelBinding[] = [];
80
+
81
+ for (const binding of bindings) {
82
+ const matchesEvent = binding.event === event;
83
+ const matchesCallback = !callback || binding.callback === callback;
84
+ if (matchesEvent && matchesCallback) {
85
+ currentSocket.off(binding.event, binding.wrapped);
86
+ } else {
87
+ kept.push(binding);
88
+ }
89
+ }
90
+
91
+ if (kept.length > 0) channels.set(room, kept);
92
+ else channels.delete(room);
93
+ return api;
94
+ }
95
+ };
96
+
97
+ return api;
98
+ };
99
+
100
+ const pulse: PulseClient = {
101
+ connect: () => {
102
+ ensureConnected();
103
+ return pulse;
104
+ },
105
+ disconnect: () => {
106
+ socket?.disconnect();
107
+ socket = null;
108
+ return pulse;
109
+ },
110
+ channel,
111
+ private: (room: string) => channel(`private:${room}`),
112
+ leave: (room: string) => {
113
+ if (!socket) return pulse;
114
+
115
+ const bindings = channels.get(room) || [];
116
+ for (const binding of bindings) {
117
+ socket.off(binding.event, binding.wrapped);
118
+ }
119
+
120
+ channels.delete(room);
121
+ joinedRooms.delete(room);
122
+ return pulse;
123
+ }
124
+ };
125
+
126
+ return pulse;
127
+ }
128
+
129
+ export const pulse = createPulse();
130
+
131
+ export const PulsePlugin = {
132
+ install(app: App) {
133
+ app.config.globalProperties.$pulse = pulse;
134
+ }
135
+ };
136
+
137
+ declare module "vue" {
138
+ interface ComponentCustomProperties {
139
+ $pulse: PulseClient;
140
+ }
141
+ }
@@ -0,0 +1,87 @@
1
+ import type { Router } from "vue-router";
2
+
3
+ const BAR_ID = "app-route-progress";
4
+
5
+ function ensureBar(): HTMLElement {
6
+ const existing = document.getElementById(BAR_ID);
7
+ if (existing) return existing;
8
+
9
+ const bar = document.createElement("div");
10
+ bar.id = BAR_ID;
11
+ bar.style.position = "fixed";
12
+ bar.style.top = "0";
13
+ bar.style.left = "0";
14
+ bar.style.right = "0";
15
+ bar.style.height = "2px";
16
+ bar.style.width = "100vw";
17
+ bar.style.opacity = "0";
18
+ bar.style.zIndex = "99999";
19
+ bar.style.pointerEvents = "none";
20
+ bar.style.background = "linear-gradient(90deg, #2563eb, #06b6d4)";
21
+ bar.style.boxShadow = "0 0 8px rgba(37, 99, 235, 0.5)";
22
+ bar.style.transformOrigin = "left center";
23
+ bar.style.transform = "scaleX(0)";
24
+ bar.style.transition = "transform 0.25s ease, opacity 0.2s ease";
25
+ document.body.appendChild(bar);
26
+ return bar;
27
+ }
28
+
29
+ export function setupRouteProgress(router: Router) {
30
+ if (typeof window === "undefined") return;
31
+
32
+ const bar = ensureBar();
33
+ let trickleTimer: number | null = null;
34
+ let finishTimer: number | null = null;
35
+
36
+ const clearTimers = () => {
37
+ if (trickleTimer !== null) {
38
+ window.clearInterval(trickleTimer);
39
+ trickleTimer = null;
40
+ }
41
+ if (finishTimer !== null) {
42
+ window.clearTimeout(finishTimer);
43
+ finishTimer = null;
44
+ }
45
+ };
46
+
47
+ const start = () => {
48
+ clearTimers();
49
+ bar.style.opacity = "1";
50
+ bar.dataset.progress = "0.18";
51
+ bar.style.transform = "scaleX(0.18)";
52
+
53
+ trickleTimer = window.setInterval(() => {
54
+ const current = Number.parseFloat(bar.dataset.progress || "0.18");
55
+ if (current >= 0.85) return;
56
+ const next = Math.min(current + Math.random() * 0.09, 0.85);
57
+ bar.dataset.progress = String(next);
58
+ bar.style.transform = `scaleX(${next})`;
59
+ }, 180);
60
+ };
61
+
62
+ const done = () => {
63
+ clearTimers();
64
+ bar.dataset.progress = "1";
65
+ bar.style.transform = "scaleX(1)";
66
+ finishTimer = window.setTimeout(() => {
67
+ bar.style.opacity = "0";
68
+ bar.style.transform = "scaleX(0)";
69
+ bar.dataset.progress = "0";
70
+ }, 220);
71
+ };
72
+
73
+ router.beforeEach((to, from) => {
74
+ if (to.path !== from.path) {
75
+ start();
76
+ }
77
+ return true;
78
+ });
79
+
80
+ router.afterEach(() => {
81
+ done();
82
+ });
83
+
84
+ router.onError(() => {
85
+ done();
86
+ });
87
+ }
@@ -0,0 +1,85 @@
1
+ import { createRouter, createWebHistory } from "vue-router";
2
+ import { setupRouteProgress } from "@/plugins/routeProgress";
3
+ import { useAuthStore } from "@/stores/auth";
4
+
5
+ export const routes = [
6
+ {
7
+ path: "/",
8
+ name: "dashlayout",
9
+ component: () => import("@/layouts/Layout/index.vue"),
10
+ redirect: { path: "/" },
11
+ children: [
12
+ {
13
+ path: "/",
14
+ name: "dashboard",
15
+ component: () => import("@/pages/dashboard/index.vue"),
16
+ meta: { requiresAuth: true }
17
+ },
18
+ ]
19
+ },
20
+ {
21
+ path: "/login",
22
+ name: "authlayout",
23
+ component: () => import("@/layouts/AuthLayout.vue"),
24
+ children: [
25
+ {
26
+ path: "/register",
27
+ name: "register",
28
+ component: () => import("@/pages/auth/register.vue"),
29
+ meta: { guestOnly: true }
30
+ },
31
+ {
32
+ path: "/login",
33
+ name: "login",
34
+ component: () => import("@/pages/auth/login.vue"),
35
+ meta: { guestOnly: true }
36
+ },
37
+ {
38
+ path: "/forget-password",
39
+ name: "forget-password",
40
+ component: () => import("@/pages/auth/forgetPassword.vue"),
41
+ meta: { guestOnly: true }
42
+ },
43
+ {
44
+ path: "/reset-password",
45
+ name: "reset-password",
46
+ component: () => import("@/pages/auth/resetPassword.vue"),
47
+ meta: { guestOnly: true }
48
+ },
49
+ {
50
+ path: "/verify-email",
51
+ name: "verify-email",
52
+ component: () => import("@/pages/auth/verifyEmail.vue"),
53
+ meta: { guestOnly: true }
54
+ }
55
+ ]
56
+ }
57
+ ];
58
+
59
+ const router = createRouter({
60
+ history: createWebHistory(),
61
+ routes,
62
+ scrollBehavior: (_to, _from, savedPosition) => {
63
+ if (savedPosition) return savedPosition;
64
+ return { top: 0 };
65
+ }
66
+ });
67
+
68
+ setupRouteProgress(router);
69
+
70
+ router.beforeEach(async (to) => {
71
+ const auth = useAuthStore();
72
+ await auth.bootstrap();
73
+
74
+ if (to.meta.requiresAuth && !auth.isAuthenticated) {
75
+ return { path: "/login", query: { redirect: to.fullPath } };
76
+ }
77
+
78
+ if (to.meta.guestOnly && auth.isAuthenticated) {
79
+ return { path: "/" };
80
+ }
81
+
82
+ return true;
83
+ });
84
+
85
+ export default router;