@imksh/create 1.0.30 → 1.0.32

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 (247) hide show
  1. package/README.md +2 -0
  2. package/package.json +1 -1
  3. package/src/commands/create.js +2 -0
  4. package/src/index.js +1 -1
  5. package/src/utils/askTemplate.js +2 -1
  6. package/templates/layers/electron/base/electron/main.js +262 -0
  7. package/templates/layers/electron/base/electron/preload.js +37 -0
  8. package/templates/layers/electron/base/electron-builder.json5 +68 -0
  9. package/templates/layers/electron/javascript/_gitignore +38 -0
  10. package/templates/layers/electron/javascript/package.json +49 -0
  11. package/templates/layers/electron/javascript/src/components/layout/SidebarLayout.jsx +43 -0
  12. package/templates/layers/electron/javascript/src/components/ui/TitleBar.jsx +130 -0
  13. package/templates/layers/electron/javascript/src/config/appConfig.js +51 -0
  14. package/templates/layers/electron/javascript/src/hooks/useElectron.js +68 -0
  15. package/templates/layers/electron/javascript/src/main.jsx +17 -0
  16. package/templates/layers/electron/javascript/src/store/useUiStore.js +59 -0
  17. package/templates/layers/electron/javascript/vite.config.js +29 -0
  18. package/templates/layers/electron/typescript/_gitignore +38 -0
  19. package/templates/layers/electron/typescript/electron/main.ts +213 -0
  20. package/templates/layers/electron/typescript/electron/preload.ts +52 -0
  21. package/templates/layers/electron/typescript/package.json +52 -0
  22. package/templates/layers/electron/typescript/src/components/layout/SidebarLayout.tsx +42 -0
  23. package/templates/layers/electron/typescript/src/components/ui/TitleBar.tsx +120 -0
  24. package/templates/layers/electron/typescript/src/config/appConfig.ts +52 -0
  25. package/templates/layers/electron/typescript/src/hooks/useElectron.ts +68 -0
  26. package/templates/layers/electron/typescript/src/main.tsx +17 -0
  27. package/templates/layers/electron/typescript/src/store/useUiStore.ts +97 -0
  28. package/templates/layers/electron/typescript/src/vite-env.d.ts +13 -0
  29. package/templates/layers/electron/typescript/tsconfig.json +25 -0
  30. package/templates/layers/electron/typescript/tsconfig.node.json +11 -0
  31. package/templates/layers/electron/typescript/vite.config.ts +29 -0
  32. package/templates/layers/next/javascript/package.json +8 -2
  33. package/templates/layers/next/javascript/src/components/common/Breadcrumbs.jsx +44 -6
  34. package/templates/layers/next/javascript/src/components/common/Loading.jsx +1 -0
  35. package/templates/layers/next/javascript/src/components/common/LottieIcon.jsx +1 -0
  36. package/templates/layers/next/javascript/src/components/common/Pagination.jsx +170 -0
  37. package/templates/layers/next/javascript/src/components/common/SidebarModal.jsx +180 -0
  38. package/templates/layers/next/javascript/src/components/common/StatsCard.jsx +168 -0
  39. package/templates/layers/next/javascript/src/components/common/Table.jsx +191 -41
  40. package/templates/layers/next/javascript/src/components/common/Tabs.jsx +187 -0
  41. package/templates/layers/next/javascript/src/components/common/ToggleSwitch.jsx +4 -3
  42. package/templates/layers/next/javascript/src/components/layout/ProtectedRoute.jsx +2 -1
  43. package/templates/layers/next/javascript/src/components/layout/PublicLayout.jsx +3 -6
  44. package/templates/layers/next/javascript/src/components/layout/SidebarLayout.jsx +4 -17
  45. package/templates/layers/next/javascript/src/components/modals/ConfirmModal.jsx +4 -12
  46. package/templates/layers/next/javascript/src/components/modals/Modal.jsx +50 -19
  47. package/templates/layers/next/javascript/src/components/ui/Input.jsx +50 -0
  48. package/templates/layers/next/{typescript/src/components/ui/sidebar.tsx → javascript/src/components/ui/Sidebar.jsx} +65 -53
  49. package/templates/layers/next/javascript/src/components/ui/SidebarHeader.jsx +11 -6
  50. package/templates/layers/next/javascript/src/config/appConfig.js +4 -4
  51. package/templates/layers/next/javascript/src/hooks/useWindowSize.js +2 -3
  52. package/templates/layers/next/javascript/src/store/useUiStore.js +9 -4
  53. package/templates/layers/next/typescript/.eslintrc.json +12 -0
  54. package/templates/layers/next/typescript/package.json +14 -3
  55. package/templates/layers/next/typescript/src/components/common/Breadcrumbs.tsx +57 -8
  56. package/templates/layers/next/typescript/src/components/common/Loading.tsx +2 -1
  57. package/templates/layers/next/typescript/src/components/common/LottieIcon.tsx +2 -2
  58. package/templates/layers/next/typescript/src/components/common/Pagination.tsx +186 -0
  59. package/templates/layers/next/typescript/src/components/common/SidebarModal.tsx +200 -0
  60. package/templates/layers/next/typescript/src/components/common/StatsCard.tsx +232 -0
  61. package/templates/layers/next/typescript/src/components/common/Table.tsx +205 -39
  62. package/templates/layers/next/typescript/src/components/common/Tabs.tsx +190 -0
  63. package/templates/layers/next/typescript/src/components/common/ToggleSwitch.tsx +12 -8
  64. package/templates/layers/next/typescript/src/components/layout/PublicLayout.tsx +8 -6
  65. package/templates/layers/next/typescript/src/components/layout/SidebarLayout.tsx +8 -17
  66. package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +3 -1
  67. package/templates/layers/next/typescript/src/components/modals/Modal.tsx +67 -21
  68. package/templates/layers/next/typescript/src/components/ui/Button.tsx +1 -0
  69. package/templates/layers/next/typescript/src/components/ui/Header.tsx +2 -2
  70. package/templates/layers/next/typescript/src/components/ui/Input.tsx +40 -60
  71. package/templates/layers/next/{javascript/src/components/ui/sidebar.jsx → typescript/src/components/ui/Sidebar.tsx} +77 -44
  72. package/templates/layers/next/typescript/src/components/ui/SidebarHeader.tsx +16 -10
  73. package/templates/layers/next/typescript/src/config/appConfig.ts +4 -4
  74. package/templates/layers/next/typescript/src/hooks/usePwaInstall.ts +10 -4
  75. package/templates/layers/next/typescript/src/hooks/useWindowSize.ts +2 -3
  76. package/templates/layers/next/typescript/src/services/authService.ts +1 -1
  77. package/templates/layers/next/typescript/src/store/useAuthStore.ts +24 -9
  78. package/templates/layers/next/typescript/src/store/useUiStore.ts +14 -4
  79. package/templates/layers/node/javascript/.env.example +3 -1
  80. package/templates/layers/node/javascript/package.json +12 -3
  81. package/templates/layers/node/javascript/src/app.js +2 -1
  82. package/templates/layers/node/javascript/src/controllers/auth.controller.js +106 -168
  83. package/templates/layers/node/javascript/src/controllers/push.controller.js +51 -58
  84. package/templates/layers/node/javascript/src/controllers/upload.controller.js +12 -19
  85. package/templates/layers/node/javascript/src/middleware/auth.middleware.js +5 -11
  86. package/templates/layers/node/javascript/src/middleware/errorHandler.js +6 -4
  87. package/templates/layers/node/javascript/src/routes/auth.route.js +2 -0
  88. package/templates/layers/node/javascript/src/services/auth.service.js +159 -164
  89. package/templates/layers/node/javascript/src/utils/ApiError.js +20 -0
  90. package/templates/layers/node/javascript/src/utils/SuccessResponse.js +15 -0
  91. package/templates/layers/node/javascript/src/utils/apiResponse.js +4 -0
  92. package/templates/layers/node/javascript/src/utils/asyncHandler.js +5 -0
  93. package/templates/layers/node/typescript/.env.example +3 -1
  94. package/templates/layers/node/typescript/.eslintrc.cjs +28 -0
  95. package/templates/layers/node/typescript/package-lock.json +2 -2
  96. package/templates/layers/node/typescript/package.json +17 -3
  97. package/templates/layers/node/typescript/src/app.ts +3 -2
  98. package/templates/layers/node/typescript/src/config/db.ts +3 -2
  99. package/templates/layers/node/typescript/src/config/socket.ts +5 -4
  100. package/templates/layers/node/typescript/src/controllers/auth.controller.ts +104 -132
  101. package/templates/layers/node/typescript/src/controllers/push.controller.ts +52 -59
  102. package/templates/layers/node/typescript/src/controllers/upload.controller.ts +12 -19
  103. package/templates/layers/node/typescript/src/middleware/auth.middleware.ts +13 -7
  104. package/templates/layers/node/typescript/src/middleware/errorHandler.ts +18 -5
  105. package/templates/layers/node/typescript/src/middleware/rateLimiter.ts +2 -2
  106. package/templates/layers/node/typescript/src/routes/auth.route.ts +2 -0
  107. package/templates/layers/node/typescript/src/services/auth.service.ts +86 -43
  108. package/templates/layers/node/typescript/src/types/express.d.ts +5 -3
  109. package/templates/layers/node/typescript/src/utils/ApiError.ts +25 -0
  110. package/templates/layers/node/typescript/src/utils/SuccessResponse.ts +17 -0
  111. package/templates/layers/node/typescript/src/utils/apiResponse.ts +4 -0
  112. package/templates/layers/node/typescript/src/utils/asyncHandler.ts +13 -0
  113. package/templates/layers/node/typescript/src/utils/cloudinaryUpload.ts +18 -7
  114. package/templates/layers/node/typescript/src/utils/expoNotification.ts +1 -1
  115. package/templates/layers/node/typescript/src/utils/genOtpToken.ts +7 -1
  116. package/templates/layers/node/typescript/src/utils/jwt.ts +8 -2
  117. package/templates/layers/node/typescript/src/utils/logger.ts +2 -1
  118. package/templates/layers/node/typescript/src/utils/pushHelper.ts +13 -9
  119. package/templates/layers/node-prisma/javascript/.env.example +3 -1
  120. package/templates/layers/node-prisma/javascript/package.json +11 -2
  121. package/templates/layers/node-prisma/javascript/src/app.js +2 -1
  122. package/templates/layers/node-prisma/javascript/src/controllers/auth.controller.js +90 -135
  123. package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +64 -76
  124. package/templates/layers/node-prisma/javascript/src/controllers/upload.controller.js +12 -19
  125. package/templates/layers/node-prisma/javascript/src/middleware/auth.middleware.js +5 -4
  126. package/templates/layers/node-prisma/javascript/src/middleware/errorHandler.js +6 -4
  127. package/templates/layers/node-prisma/javascript/src/routes/auth.route.js +2 -0
  128. package/templates/layers/node-prisma/javascript/src/services/auth.service.js +145 -122
  129. package/templates/layers/node-prisma/javascript/src/utils/ApiError.js +20 -0
  130. package/templates/layers/node-prisma/javascript/src/utils/SuccessResponse.js +15 -0
  131. package/templates/layers/node-prisma/javascript/src/utils/apiResponse.js +4 -0
  132. package/templates/layers/node-prisma/javascript/src/utils/asyncHandler.js +5 -0
  133. package/templates/layers/node-prisma/typescript/.env.example +3 -1
  134. package/templates/layers/node-prisma/typescript/.eslintrc.cjs +28 -0
  135. package/templates/layers/node-prisma/typescript/package.json +19 -5
  136. package/templates/layers/node-prisma/typescript/src/app.ts +3 -2
  137. package/templates/layers/node-prisma/typescript/src/config/prisma.ts +3 -2
  138. package/templates/layers/node-prisma/typescript/src/config/socket.ts +5 -4
  139. package/templates/layers/node-prisma/typescript/src/controllers/auth.controller.ts +89 -105
  140. package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -83
  141. package/templates/layers/node-prisma/typescript/src/controllers/upload.controller.ts +12 -19
  142. package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +9 -13
  143. package/templates/layers/node-prisma/typescript/src/middleware/errorHandler.ts +18 -5
  144. package/templates/layers/node-prisma/typescript/src/middleware/rateLimiter.ts +2 -2
  145. package/templates/layers/node-prisma/typescript/src/routes/auth.route.ts +2 -0
  146. package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +83 -31
  147. package/templates/layers/node-prisma/typescript/src/types/express.d.ts +5 -3
  148. package/templates/layers/node-prisma/typescript/src/utils/ApiError.ts +25 -0
  149. package/templates/layers/node-prisma/typescript/src/utils/SuccessResponse.ts +17 -0
  150. package/templates/layers/node-prisma/typescript/src/utils/apiResponse.ts +4 -0
  151. package/templates/layers/node-prisma/typescript/src/utils/asyncHandler.ts +13 -0
  152. package/templates/layers/node-prisma/typescript/src/utils/cloudinaryUpload.ts +18 -7
  153. package/templates/layers/node-prisma/typescript/src/utils/genOtpToken.ts +7 -1
  154. package/templates/layers/node-prisma/typescript/src/utils/jwt.ts +8 -2
  155. package/templates/layers/node-prisma/typescript/src/utils/logger.ts +2 -1
  156. package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +10 -6
  157. package/templates/layers/prisma/javascript/package.json +8 -0
  158. package/templates/layers/prisma/typescript/package.json +14 -1
  159. package/templates/layers/prisma/typescript/prisma/schema.prisma +35 -10
  160. package/templates/layers/prisma/typescript/src/config/db.ts +3 -2
  161. package/templates/layers/react/javascript/package.json +6 -0
  162. package/templates/layers/react/javascript/src/components/common/Breadcrumbs.jsx +62 -58
  163. package/templates/layers/react/javascript/src/components/common/Pagination.jsx +164 -76
  164. package/templates/layers/react/javascript/src/components/common/SidebarModal.jsx +179 -0
  165. package/templates/layers/react/javascript/src/components/common/StatsCard.jsx +167 -0
  166. package/templates/layers/react/javascript/src/components/common/Table.jsx +248 -132
  167. package/templates/layers/react/javascript/src/components/common/Tabs.jsx +164 -112
  168. package/templates/layers/react/javascript/src/components/common/ToggleSwitch.jsx +3 -3
  169. package/templates/layers/react/javascript/src/components/layout/SidebarLayout.jsx +4 -12
  170. package/templates/layers/react/javascript/src/components/modals/Modal.jsx +53 -18
  171. package/templates/layers/react/javascript/src/components/ui/Input.jsx +49 -0
  172. package/templates/layers/react/javascript/src/components/ui/Sidebar.jsx +215 -0
  173. package/templates/layers/react/javascript/src/components/ui/SidebarHeader.jsx +25 -30
  174. package/templates/layers/react/javascript/src/config/appConfig.js +3 -3
  175. package/templates/layers/react/javascript/src/hooks/useWindowSize.js +2 -2
  176. package/templates/layers/react/typescript/.eslintrc.cjs +20 -0
  177. package/templates/layers/react/typescript/package.json +11 -2
  178. package/templates/layers/react/typescript/src/components/common/Breadcrumbs.tsx +32 -6
  179. package/templates/layers/react/typescript/src/components/common/Pagination.tsx +151 -57
  180. package/templates/layers/react/typescript/src/components/common/SidebarModal.tsx +199 -0
  181. package/templates/layers/react/typescript/src/components/common/StatsCard.tsx +231 -0
  182. package/templates/layers/react/typescript/src/components/common/Table.tsx +201 -38
  183. package/templates/layers/react/typescript/src/components/common/Tabs.tsx +56 -12
  184. package/templates/layers/react/typescript/src/components/common/ToggleSwitch.tsx +15 -3
  185. package/templates/layers/react/typescript/src/components/layout/SidebarLayout.tsx +3 -6
  186. package/templates/layers/react/typescript/src/components/modals/Modal.tsx +151 -0
  187. package/templates/layers/react/typescript/src/components/ui/Input.tsx +58 -0
  188. package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +116 -53
  189. package/templates/layers/react/typescript/src/components/ui/SidebarHeader.tsx +5 -4
  190. package/templates/layers/react/typescript/src/config/appConfig.ts +3 -3
  191. package/templates/layers/react/typescript/src/hooks/useWindowSize.ts +2 -3
  192. package/templates/layers/react/typescript/src/main.tsx +11 -11
  193. package/templates/layers/react/typescript/src/services/authService.ts +1 -1
  194. package/templates/layers/react/typescript/src/store/useAuthStore.ts +24 -9
  195. package/templates/layers/react-native/base/app.json +9 -22
  196. package/templates/layers/react-native/base/eslint.config.js +5 -1
  197. package/templates/layers/react-native/base/package.json +11 -1
  198. package/templates/layers/react-native/base/src/app/(auth)/edit-profile.tsx +7 -7
  199. package/templates/layers/react-native/base/src/app/(auth)/login.tsx +31 -7
  200. package/templates/layers/react-native/base/src/app/(tabs)/_layout.tsx +3 -3
  201. package/templates/layers/react-native/base/src/app/(tabs)/index.tsx +6 -5
  202. package/templates/layers/react-native/base/src/app/_layout.tsx +3 -3
  203. package/templates/layers/react-native/base/src/app/doc-viewer.tsx +3 -3
  204. package/templates/layers/react-native/base/src/components/common/ConfirmationToast.tsx +58 -40
  205. package/templates/layers/react-native/base/src/components/common/GoogleAuthButton.tsx +144 -0
  206. package/templates/layers/react-native/base/src/components/common/LocationLoader.tsx +64 -9
  207. package/templates/layers/react-native/base/src/components/common/Typography.tsx +54 -19
  208. package/templates/layers/react-native/base/src/components/ui/ClassicTabBar.tsx +15 -12
  209. package/templates/layers/react-native/base/src/components/ui/CustomTabBar.tsx +84 -90
  210. package/templates/layers/react-native/base/src/components/ui/Drawer.tsx +344 -0
  211. package/templates/layers/react-native/base/src/components/ui/Sidebar.tsx +4 -4
  212. package/templates/layers/react-native/base/src/components/ui/buttons/Button.tsx +29 -6
  213. package/templates/layers/react-native/base/src/components/ui/data-display/Accordion.tsx +2 -2
  214. package/templates/layers/react-native/base/src/components/ui/data-display/Badge.tsx +1 -2
  215. package/templates/layers/react-native/base/src/components/ui/data-display/Image.tsx +3 -3
  216. package/templates/layers/react-native/base/src/components/ui/data-display/SwipeableRow.tsx +28 -7
  217. package/templates/layers/react-native/base/src/components/ui/feedback/GestureWrapper.tsx +11 -4
  218. package/templates/layers/react-native/base/src/components/ui/inputs/DatePicker.tsx +3 -3
  219. package/templates/layers/react-native/base/src/components/ui/inputs/ImagePickerModal.tsx +45 -19
  220. package/templates/layers/react-native/base/src/components/ui/inputs/Input.tsx +28 -9
  221. package/templates/layers/react-native/base/src/components/ui/inputs/SearchablePicker.tsx +199 -0
  222. package/templates/layers/react-native/base/src/components/ui/inputs/Switch.tsx +7 -7
  223. package/templates/layers/react-native/base/src/components/ui/inputs/WebLocationPicker.tsx +146 -72
  224. package/templates/layers/react-native/base/src/config/api.ts +5 -5
  225. package/templates/layers/react-native/base/src/config/appConfig.ts +1 -1
  226. package/templates/layers/react-native/base/src/services/auth.service.ts +3 -1
  227. package/templates/layers/react-native/base/src/services/permission.service.ts +0 -2
  228. package/templates/layers/react-native/base/src/services/socket.service.ts +0 -7
  229. package/templates/layers/react-native/base/src/store/useAuthStore.ts +61 -16
  230. package/templates/layers/react-native/base/src/store/useNotificationStore.ts +36 -42
  231. package/templates/layers/react-native/base/src/types/images.d.ts +7 -5
  232. package/templates/layers/react-native/base/src/utils/fileDownload.ts +206 -0
  233. package/templates/layers/react-native/base/src/utils/storage.ts +1 -1
  234. package/templates/presets/electron/preset.json +9 -0
  235. package/templates/presets/electron-ts/preset.json +9 -0
  236. package/templates/presets/production-fullstack/overrides/client/package.json +9 -0
  237. package/templates/presets/production-fullstack/overrides/mobile/app.json +2 -15
  238. package/templates/presets/production-fullstack/overrides/mobile/package.json +11 -1
  239. package/templates/presets/production-fullstack/overrides/mobile/src/app/_layout.tsx +3 -3
  240. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/Drawer.tsx +344 -0
  241. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/DatePicker.tsx +3 -3
  242. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/ImagePickerModal.tsx +3 -3
  243. package/templates/presets/production-fullstack/overrides/server/package.json +11 -2
  244. package/templates/layers/react/javascript/src/components/ui/sidebar.jsx +0 -169
  245. package/templates/layers/react-native/base/src/components/ui/BottomModal.tsx +0 -176
  246. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/BottomModal.tsx +0 -159
  247. /package/templates/layers/react-native/base/assets/images/logo/{logoWithBg.png → icon.png} +0 -0
@@ -0,0 +1,51 @@
1
+ const appConfig = {
2
+ app: {
3
+ name: "Electron App",
4
+ logo: "./logo.png",
5
+ shortName: "App",
6
+ description: "An Electron application built with @imksh/create",
7
+ version: "1.0.0",
8
+ author: "imksh",
9
+ },
10
+
11
+ api: {
12
+ baseURL: import.meta.env.VITE_API_URL,
13
+ timeout: 30000,
14
+ },
15
+
16
+ routes: {
17
+ home: "/",
18
+ login: "/login",
19
+ profile: "/profile",
20
+ },
21
+
22
+ pagination: {
23
+ pageSize: 10,
24
+ },
25
+
26
+ github: {
27
+ repository: "",
28
+ },
29
+
30
+ social: {
31
+ website: "",
32
+ linkedin: "",
33
+ github: "",
34
+ twitter: "",
35
+ },
36
+
37
+ features: {
38
+ darkMode: true,
39
+ notifications: true,
40
+ sidebar: true,
41
+ electron: true,
42
+ customTitleBar: true,
43
+ },
44
+
45
+ electron: {
46
+ tray: true,
47
+ autoUpdater: false, // Set to true and configure publish in electron-builder.json5 to enable
48
+ },
49
+ };
50
+
51
+ export default appConfig;
@@ -0,0 +1,68 @@
1
+ import { useEffect } from "react";
2
+
3
+ /**
4
+ * useElectron — safely access window.electronAPI in the renderer.
5
+ *
6
+ * Returns typed wrappers for all IPC calls. Every method is a no-op
7
+ * (or returns a resolved promise) when running outside of Electron
8
+ * (e.g. during browser-based development or testing).
9
+ *
10
+ * @returns {{ isElectron: boolean } & ElectronAPI}
11
+ */
12
+ export function useElectron() {
13
+ const isElectron = typeof window !== "undefined" && Boolean(window.electronAPI);
14
+
15
+ return {
16
+ isElectron,
17
+
18
+ // Window controls
19
+ minimize: () => window.electronAPI?.minimize(),
20
+ maximize: () => window.electronAPI?.maximize(),
21
+ close: () => window.electronAPI?.close(),
22
+
23
+ // Window state
24
+ isMaximized: () => window.electronAPI?.isMaximized() ?? Promise.resolve(false),
25
+
26
+ // App info
27
+ getVersion: () => window.electronAPI?.getVersion() ?? Promise.resolve("web"),
28
+ getPlatform: () => window.electronAPI?.getPlatform() ?? Promise.resolve("web"),
29
+
30
+ // Shell
31
+ openExternal: (url) =>
32
+ window.electronAPI?.openExternal(url) ?? Promise.resolve(),
33
+
34
+ // Event subscription helpers
35
+ onMaximizeChange: (cb) => window.electronAPI?.onMaximizeChange(cb),
36
+ onMenuAction: (cb) => window.electronAPI?.onMenuAction(cb),
37
+
38
+ // Cleanup
39
+ removeAllListeners: (channel) =>
40
+ window.electronAPI?.removeAllListeners(channel),
41
+ };
42
+ }
43
+
44
+ /**
45
+ * useElectronEvent — subscribe to an Electron IPC event in a React component.
46
+ *
47
+ * Automatically cleans up the listener on unmount.
48
+ *
49
+ * @param {"maximize" | "menu"} event
50
+ * @param {Function} callback
51
+ */
52
+ export function useElectronEvent(event, callback) {
53
+ const electron = useElectron();
54
+
55
+ useEffect(() => {
56
+ if (!electron.isElectron) return;
57
+
58
+ if (event === "maximize") {
59
+ electron.onMaximizeChange(callback);
60
+ return () => electron.removeAllListeners("window:maximized-change");
61
+ }
62
+
63
+ if (event === "menu") {
64
+ electron.onMenuAction(callback);
65
+ return () => electron.removeAllListeners("menu:preferences");
66
+ }
67
+ }, [event, callback, electron.isElectron]);
68
+ }
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import { HashRouter } from "react-router-dom";
4
+ import App from "./App.jsx";
5
+ import "./index.css";
6
+
7
+ // HashRouter is required for Electron because the renderer is served via
8
+ // the file:// protocol in production, which breaks BrowserRouter's history API.
9
+ // Hash-based URLs (#/path) work correctly in both dev and packaged builds.
10
+
11
+ ReactDOM.createRoot(document.getElementById("root")).render(
12
+ <React.StrictMode>
13
+ <HashRouter>
14
+ <App />
15
+ </HashRouter>
16
+ </React.StrictMode>,
17
+ );
@@ -0,0 +1,59 @@
1
+ import { create } from "zustand";
2
+
3
+ const useUiStore = create((set, get) => ({
4
+ open: false,
5
+ isModalOpen: false,
6
+ collapsed: false,
7
+ isMobileMenuOpened: false,
8
+ headerTitle: "Dashboard",
9
+ headerActions: null,
10
+ breadcrumbs: null,
11
+ layoutClass: "",
12
+
13
+ // ── Layout helpers ──────────────────────────────────────────────────────────
14
+ setLayoutClass: (layoutClass) => set({ layoutClass }),
15
+ setHeaderTitle: (title) => set({ headerTitle: title }),
16
+ setHeaderActions: (actions) => set({ headerActions: actions }),
17
+ setBreadcrumbs: (breadcrumbs) => set({ breadcrumbs }),
18
+
19
+ // ── Sidebar ─────────────────────────────────────────────────────────────────
20
+ toggleCollapsed: () => set((state) => ({ collapsed: !state.collapsed })),
21
+ setMobileMenuOpened: (open) => set({ isMobileMenuOpened: open }),
22
+ toggleMobileMenuOpened: () =>
23
+ set((state) => ({ isMobileMenuOpened: !state.isMobileMenuOpened })),
24
+ toggleSidebar: () => set((state) => ({ open: !state.open })),
25
+ openSidebar: () => set({ open: true }),
26
+ close: () => set({ open: false }),
27
+
28
+ // ── Modal ───────────────────────────────────────────────────────────────────
29
+ setModalOpen: (open) => set({ isModalOpen: open }),
30
+
31
+ // ── Theme ───────────────────────────────────────────────────────────────────
32
+ theme: localStorage.getItem("theme") || "light",
33
+ setTheme: (theme) => {
34
+ document.documentElement.setAttribute("data-theme", theme);
35
+ localStorage.setItem("theme", theme);
36
+ set({ theme });
37
+ },
38
+ toggleTheme: () => {
39
+ const nextTheme = get().theme === "light" ? "dark" : "light";
40
+ document.documentElement.setAttribute("data-theme", nextTheme);
41
+ localStorage.setItem("theme", nextTheme);
42
+ set({ theme: nextTheme });
43
+ },
44
+
45
+ // ── Electron window state ───────────────────────────────────────────────────
46
+ windowMaximized: false,
47
+ setWindowMaximized: (value) => set({ windowMaximized: value }),
48
+
49
+ /** Minimize the Electron window (no-op in browser) */
50
+ minimizeWindow: () => window.electronAPI?.minimize(),
51
+
52
+ /** Toggle maximize / restore the Electron window (no-op in browser) */
53
+ maximizeWindow: () => window.electronAPI?.maximize(),
54
+
55
+ /** Close the Electron window (no-op in browser) */
56
+ closeWindow: () => window.electronAPI?.close(),
57
+ }));
58
+
59
+ export { useUiStore };
@@ -0,0 +1,29 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import tailwindcss from '@tailwindcss/vite';
4
+ import electron from 'vite-plugin-electron';
5
+ import renderer from 'vite-plugin-electron-renderer';
6
+
7
+ // https://vitejs.dev/config/
8
+ export default defineConfig({
9
+ plugins: [
10
+ react(),
11
+ tailwindcss(),
12
+ electron([
13
+ {
14
+ // Main process entry
15
+ entry: 'electron/main.js',
16
+ },
17
+ {
18
+ // Preload script — reload renderer on change
19
+ entry: 'electron/preload.js',
20
+ onstart(options) {
21
+ options.reload();
22
+ },
23
+ },
24
+ ]),
25
+ renderer(),
26
+ ],
27
+ // Critical: ensures built assets resolve correctly via file:// in production
28
+ base: './',
29
+ });
@@ -0,0 +1,38 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ dist-electron
14
+ *.local
15
+
16
+ # Electron build output
17
+ release/
18
+ out/
19
+
20
+ # Electron platform artifacts
21
+ *.dmg
22
+ *.exe
23
+ *.msi
24
+ *.AppImage
25
+ *.deb
26
+ *.snap
27
+ *.rpm
28
+
29
+ # Editor directories and files
30
+ .vscode/*
31
+ !.vscode/extensions.json
32
+ .idea
33
+ .DS_Store
34
+ *.suo
35
+ *.ntvs*
36
+ *.njsproj
37
+ *.sln
38
+ *.sw?
@@ -0,0 +1,213 @@
1
+ import {
2
+ app,
3
+ BrowserWindow,
4
+ Menu,
5
+ Tray,
6
+ shell,
7
+ ipcMain,
8
+ nativeImage,
9
+ MenuItemConstructorOptions,
10
+ } from 'electron';
11
+ import { fileURLToPath } from 'url';
12
+ import path from 'path';
13
+
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+
16
+ // ─── Constants ────────────────────────────────────────────────────────────────
17
+ const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
18
+ const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL'];
19
+ const preloadPath = path.join(__dirname, 'preload.js');
20
+
21
+ let mainWindow: BrowserWindow | null = null;
22
+ let tray: Tray | null = null;
23
+
24
+ // ─── Window ───────────────────────────────────────────────────────────────────
25
+ function createWindow(): void {
26
+ mainWindow = new BrowserWindow({
27
+ width: 1280,
28
+ height: 800,
29
+ minWidth: 800,
30
+ minHeight: 600,
31
+ frame: false,
32
+ titleBarStyle: 'hidden',
33
+ trafficLightPosition: { x: 16, y: 16 },
34
+ backgroundColor: '#0f172a',
35
+ show: false,
36
+ icon: path.join(__dirname, '../public/icon.png'),
37
+ webPreferences: {
38
+ preload: preloadPath,
39
+ contextIsolation: true,
40
+ nodeIntegration: false,
41
+ sandbox: false,
42
+ },
43
+ });
44
+
45
+ mainWindow.once('ready-to-show', () => {
46
+ mainWindow!.show();
47
+ if (isDev) mainWindow!.webContents.openDevTools({ mode: 'detach' });
48
+ });
49
+
50
+ mainWindow.on('maximize', () => mainWindow?.webContents.send('window:maximized-change', true));
51
+ mainWindow.on('unmaximize', () => mainWindow?.webContents.send('window:maximized-change', false));
52
+
53
+ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
54
+ shell.openExternal(url);
55
+ return { action: 'deny' };
56
+ });
57
+
58
+ if (isDev && VITE_DEV_SERVER_URL) {
59
+ mainWindow.loadURL(VITE_DEV_SERVER_URL);
60
+ } else {
61
+ mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
62
+ }
63
+ }
64
+
65
+ // ─── Native Application Menu ──────────────────────────────────────────────────
66
+ function buildMenu(): void {
67
+ const isMac = process.platform === 'darwin';
68
+
69
+ const template: MenuItemConstructorOptions[] = [
70
+ ...(isMac
71
+ ? [{
72
+ label: app.name,
73
+ submenu: [
74
+ { role: 'about' as const },
75
+ { type: 'separator' as const },
76
+ { label: 'Preferences…', accelerator: 'CmdOrCtrl+,', click: () => mainWindow?.webContents.send('menu:preferences') },
77
+ { type: 'separator' as const },
78
+ { role: 'services' as const },
79
+ { type: 'separator' as const },
80
+ { role: 'hide' as const },
81
+ { role: 'hideOthers' as const },
82
+ { role: 'unhide' as const },
83
+ { type: 'separator' as const },
84
+ { role: 'quit' as const },
85
+ ],
86
+ }]
87
+ : []),
88
+
89
+ {
90
+ label: 'File',
91
+ submenu: [
92
+ { label: 'New Window', accelerator: 'CmdOrCtrl+Shift+N', click: () => createWindow() },
93
+ { type: 'separator' },
94
+ isMac ? { role: 'close' as const } : { role: 'quit' as const },
95
+ ],
96
+ },
97
+
98
+ {
99
+ label: 'Edit',
100
+ submenu: [
101
+ { role: 'undo' as const },
102
+ { role: 'redo' as const },
103
+ { type: 'separator' as const },
104
+ { role: 'cut' as const },
105
+ { role: 'copy' as const },
106
+ { role: 'paste' as const },
107
+ ...(isMac
108
+ ? [{ role: 'pasteAndMatchStyle' as const }, { role: 'delete' as const }, { role: 'selectAll' as const }]
109
+ : [{ role: 'delete' as const }, { type: 'separator' as const }, { role: 'selectAll' as const }]),
110
+ ],
111
+ },
112
+
113
+ {
114
+ label: 'View',
115
+ submenu: [
116
+ { role: 'reload' as const },
117
+ { role: 'forceReload' as const },
118
+ { role: 'toggleDevTools' as const },
119
+ { type: 'separator' as const },
120
+ { role: 'resetZoom' as const },
121
+ { role: 'zoomIn' as const },
122
+ { role: 'zoomOut' as const },
123
+ { type: 'separator' as const },
124
+ { role: 'togglefullscreen' as const },
125
+ ],
126
+ },
127
+
128
+ {
129
+ label: 'Window',
130
+ submenu: [
131
+ { role: 'minimize' as const },
132
+ { role: 'zoom' as const },
133
+ ...(isMac
134
+ ? [{ type: 'separator' as const }, { role: 'front' as const }, { type: 'separator' as const }, { role: 'window' as const }]
135
+ : [{ role: 'close' as const }]),
136
+ ],
137
+ },
138
+
139
+ {
140
+ role: 'help' as const,
141
+ submenu: [
142
+ { label: 'Learn More', click: () => shell.openExternal('https://github.com/imksh/create') },
143
+ {
144
+ label: 'Toggle Developer Tools',
145
+ accelerator: isMac ? 'Alt+Cmd+I' : 'Alt+Shift+I',
146
+ click: () => mainWindow?.webContents.toggleDevTools(),
147
+ },
148
+ ],
149
+ },
150
+ ];
151
+
152
+ Menu.setApplicationMenu(Menu.buildFromTemplate(template));
153
+ }
154
+
155
+ // ─── System Tray ──────────────────────────────────────────────────────────────
156
+ function createTray(): void {
157
+ const iconPath = path.join(__dirname, '../public/icon.png');
158
+ const trayIcon = nativeImage.createFromPath(iconPath).resize({ width: 16, height: 16 });
159
+ tray = new Tray(trayIcon);
160
+
161
+ const contextMenu = Menu.buildFromTemplate([
162
+ { label: 'Show App', click: () => { mainWindow?.show(); mainWindow?.focus(); } },
163
+ { type: 'separator' },
164
+ { label: 'Quit', click: () => { (app as any).isQuiting = true; app.quit(); } },
165
+ ]);
166
+
167
+ tray.setToolTip(app.name);
168
+ tray.setContextMenu(contextMenu);
169
+ tray.on('double-click', () => { mainWindow?.show(); mainWindow?.focus(); });
170
+ }
171
+
172
+ // ─── IPC Handlers ─────────────────────────────────────────────────────────────
173
+ function registerIpcHandlers(): void {
174
+ ipcMain.on('window:minimize', () => mainWindow?.minimize());
175
+ ipcMain.on('window:maximize', () => {
176
+ mainWindow?.isMaximized() ? mainWindow.unmaximize() : mainWindow?.maximize();
177
+ });
178
+ ipcMain.on('window:close', () => mainWindow?.close());
179
+
180
+ ipcMain.handle('window:isMaximized', () => mainWindow?.isMaximized() ?? false);
181
+ ipcMain.handle('app:version', () => app.getVersion());
182
+ ipcMain.handle('app:platform', () => process.platform);
183
+
184
+ ipcMain.handle('shell:openExternal', (_event, url: string) => {
185
+ if (typeof url === 'string' && url.startsWith('http')) {
186
+ return shell.openExternal(url);
187
+ }
188
+ });
189
+ }
190
+
191
+ // ─── App Lifecycle ────────────────────────────────────────────────────────────
192
+ app.whenReady().then(() => {
193
+ if (process.platform === 'darwin') {
194
+ app.setAboutPanelOptions({
195
+ applicationName: app.name,
196
+ applicationVersion: app.getVersion(),
197
+ copyright: `© ${new Date().getFullYear()} imksh`,
198
+ });
199
+ }
200
+
201
+ registerIpcHandlers();
202
+ buildMenu();
203
+ createWindow();
204
+ createTray();
205
+
206
+ app.on('activate', () => {
207
+ if (BrowserWindow.getAllWindows().length === 0) createWindow();
208
+ });
209
+ });
210
+
211
+ app.on('window-all-closed', () => {
212
+ if (process.platform !== 'darwin') app.quit();
213
+ });
@@ -0,0 +1,52 @@
1
+ import { contextBridge, ipcRenderer } from 'electron';
2
+
3
+ /**
4
+ * ElectronAPI — typed interface exposed to the renderer via window.electronAPI.
5
+ * Declare this in vite-env.d.ts to get full type safety in the renderer.
6
+ */
7
+ export interface ElectronAPI {
8
+ // Window controls
9
+ minimize: () => void;
10
+ maximize: () => void;
11
+ close: () => void;
12
+
13
+ // Window state
14
+ isMaximized: () => Promise<boolean>;
15
+
16
+ // App info
17
+ getVersion: () => Promise<string>;
18
+ getPlatform: () => Promise<NodeJS.Platform>;
19
+
20
+ // Shell
21
+ openExternal: (url: string) => Promise<void>;
22
+
23
+ // Event subscriptions
24
+ onMaximizeChange: (callback: (isMaximized: boolean) => void) => void;
25
+ onMenuAction: (callback: (data: { label: string }) => void) => void;
26
+
27
+ // Cleanup
28
+ removeAllListeners: (channel: string) => void;
29
+ }
30
+
31
+ contextBridge.exposeInMainWorld('electronAPI', {
32
+ minimize: () => ipcRenderer.send('window:minimize'),
33
+ maximize: () => ipcRenderer.send('window:maximize'),
34
+ close: () => ipcRenderer.send('window:close'),
35
+
36
+ isMaximized: () => ipcRenderer.invoke('window:isMaximized'),
37
+
38
+ getVersion: () => ipcRenderer.invoke('app:version'),
39
+ getPlatform: () => ipcRenderer.invoke('app:platform'),
40
+
41
+ openExternal: (url: string) => ipcRenderer.invoke('shell:openExternal', url),
42
+
43
+ onMaximizeChange: (callback: (isMaximized: boolean) => void) => {
44
+ ipcRenderer.on('window:maximized-change', (_event, value: boolean) => callback(value));
45
+ },
46
+
47
+ onMenuAction: (callback: (data: { label: string }) => void) => {
48
+ ipcRenderer.on('menu:preferences', (_event, data) => callback({ label: 'preferences', ...data }));
49
+ },
50
+
51
+ removeAllListeners: (channel: string) => ipcRenderer.removeAllListeners(channel),
52
+ } satisfies ElectronAPI);
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "electron-app",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "description": "An Electron + Vite + React (TypeScript) application built with @imksh/create",
6
+ "author": "imksh",
7
+ "main": "dist-electron/main.js",
8
+ "scripts": {
9
+ "dev": "vite",
10
+ "build": "tsc && vite build && electron-builder",
11
+ "preview": "electron .",
12
+ "pack": "tsc && vite build && electron-builder --dir",
13
+ "dist": "tsc && vite build && electron-builder",
14
+ "dist:mac": "tsc && vite build && electron-builder --mac",
15
+ "dist:win": "tsc && vite build && electron-builder --win",
16
+ "dist:linux": "tsc && vite build && electron-builder --linux",
17
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
18
+ },
19
+ "dependencies": {
20
+ "@tailwindcss/vite": "^4.3.2",
21
+ "axios": "^1.6.0",
22
+ "flyonui": "^2.4.1",
23
+ "framer-motion": "^11.0.0",
24
+ "lenis": "^1.1.3",
25
+ "lottie-react": "^2.4.0",
26
+ "lucide-react": "^0.292.0",
27
+ "react": "^18.2.0",
28
+ "react-dom": "^18.2.0",
29
+ "react-hot-toast": "^2.4.1",
30
+ "react-icons": "^4.11.0",
31
+ "react-router-dom": "^6.18.0",
32
+ "tailwindcss": "^4.3.2",
33
+ "zustand": "^4.5.2"
34
+ },
35
+ "devDependencies": {
36
+ "@types/react": "^18.2.37",
37
+ "@types/react-dom": "^18.2.15",
38
+ "@typescript-eslint/eslint-plugin": "^6.10.0",
39
+ "@typescript-eslint/parser": "^6.10.0",
40
+ "@vitejs/plugin-react": "^4.2.0",
41
+ "electron": "^31.0.0",
42
+ "electron-builder": "^24.13.0",
43
+ "eslint": "^8.53.0",
44
+ "eslint-plugin-react": "^7.33.2",
45
+ "eslint-plugin-react-hooks": "^4.6.0",
46
+ "eslint-plugin-react-refresh": "^0.4.4",
47
+ "typescript": "^5.2.2",
48
+ "vite": "^5.0.0",
49
+ "vite-plugin-electron": "^0.28.0",
50
+ "vite-plugin-electron-renderer": "^0.14.0"
51
+ }
52
+ }
@@ -0,0 +1,42 @@
1
+ import { useEffect } from "react";
2
+ import { Outlet, useLocation } from "react-router-dom";
3
+ import SidebarHeader from "../ui/SidebarHeader";
4
+ import Sidebar from "../ui/sidebar";
5
+ import TitleBar from "../ui/TitleBar";
6
+ import { useUiStore } from "../../store/useUiStore";
7
+ import appConfig from "../../config/appConfig";
8
+
9
+ const SidebarLayout: React.FC = () => {
10
+ const { headerTitle, headerActions, setHeaderTitle, setHeaderActions } =
11
+ useUiStore();
12
+ const { pathname } = useLocation();
13
+
14
+ useEffect(() => {
15
+ setHeaderTitle(appConfig.app.name);
16
+ setHeaderActions(null);
17
+ }, [pathname, setHeaderTitle, setHeaderActions]);
18
+
19
+ return (
20
+ <div className="flex flex-col h-screen overflow-hidden">
21
+ {/* Electron custom title bar — only renders inside Electron */}
22
+ <TitleBar />
23
+
24
+ <div className="flex flex-1 overflow-hidden">
25
+ <Sidebar />
26
+
27
+ <div className="flex flex-1 flex-col overflow-hidden">
28
+ <SidebarHeader title={headerTitle} actions={headerActions} />
29
+
30
+ <main
31
+ className="flex-1 h-full overflow-y-auto px-2 py-6 md:p-6 max-w-7xl"
32
+ data-lenis-prevent
33
+ >
34
+ <Outlet />
35
+ </main>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ );
40
+ };
41
+
42
+ export default SidebarLayout;