@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.
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/commands/create.js +2 -0
- package/src/index.js +1 -1
- package/src/utils/askTemplate.js +2 -1
- package/templates/layers/electron/base/electron/main.js +262 -0
- package/templates/layers/electron/base/electron/preload.js +37 -0
- package/templates/layers/electron/base/electron-builder.json5 +68 -0
- package/templates/layers/electron/javascript/_gitignore +38 -0
- package/templates/layers/electron/javascript/package.json +49 -0
- package/templates/layers/electron/javascript/src/components/layout/SidebarLayout.jsx +43 -0
- package/templates/layers/electron/javascript/src/components/ui/TitleBar.jsx +130 -0
- package/templates/layers/electron/javascript/src/config/appConfig.js +51 -0
- package/templates/layers/electron/javascript/src/hooks/useElectron.js +68 -0
- package/templates/layers/electron/javascript/src/main.jsx +17 -0
- package/templates/layers/electron/javascript/src/store/useUiStore.js +59 -0
- package/templates/layers/electron/javascript/vite.config.js +29 -0
- package/templates/layers/electron/typescript/_gitignore +38 -0
- package/templates/layers/electron/typescript/electron/main.ts +213 -0
- package/templates/layers/electron/typescript/electron/preload.ts +52 -0
- package/templates/layers/electron/typescript/package.json +52 -0
- package/templates/layers/electron/typescript/src/components/layout/SidebarLayout.tsx +42 -0
- package/templates/layers/electron/typescript/src/components/ui/TitleBar.tsx +120 -0
- package/templates/layers/electron/typescript/src/config/appConfig.ts +52 -0
- package/templates/layers/electron/typescript/src/hooks/useElectron.ts +68 -0
- package/templates/layers/electron/typescript/src/main.tsx +17 -0
- package/templates/layers/electron/typescript/src/store/useUiStore.ts +97 -0
- package/templates/layers/electron/typescript/src/vite-env.d.ts +13 -0
- package/templates/layers/electron/typescript/tsconfig.json +25 -0
- package/templates/layers/electron/typescript/tsconfig.node.json +11 -0
- package/templates/layers/electron/typescript/vite.config.ts +29 -0
- package/templates/layers/next/javascript/package.json +8 -2
- package/templates/layers/next/javascript/src/components/common/Breadcrumbs.jsx +44 -6
- package/templates/layers/next/javascript/src/components/common/Loading.jsx +1 -0
- package/templates/layers/next/javascript/src/components/common/LottieIcon.jsx +1 -0
- package/templates/layers/next/javascript/src/components/common/Pagination.jsx +170 -0
- package/templates/layers/next/javascript/src/components/common/SidebarModal.jsx +180 -0
- package/templates/layers/next/javascript/src/components/common/StatsCard.jsx +168 -0
- package/templates/layers/next/javascript/src/components/common/Table.jsx +191 -41
- package/templates/layers/next/javascript/src/components/common/Tabs.jsx +187 -0
- package/templates/layers/next/javascript/src/components/common/ToggleSwitch.jsx +4 -3
- package/templates/layers/next/javascript/src/components/layout/ProtectedRoute.jsx +2 -1
- package/templates/layers/next/javascript/src/components/layout/PublicLayout.jsx +3 -6
- package/templates/layers/next/javascript/src/components/layout/SidebarLayout.jsx +4 -17
- package/templates/layers/next/javascript/src/components/modals/ConfirmModal.jsx +4 -12
- package/templates/layers/next/javascript/src/components/modals/Modal.jsx +50 -19
- package/templates/layers/next/javascript/src/components/ui/Input.jsx +50 -0
- package/templates/layers/next/{typescript/src/components/ui/sidebar.tsx → javascript/src/components/ui/Sidebar.jsx} +65 -53
- package/templates/layers/next/javascript/src/components/ui/SidebarHeader.jsx +11 -6
- package/templates/layers/next/javascript/src/config/appConfig.js +4 -4
- package/templates/layers/next/javascript/src/hooks/useWindowSize.js +2 -3
- package/templates/layers/next/javascript/src/store/useUiStore.js +9 -4
- package/templates/layers/next/typescript/.eslintrc.json +12 -0
- package/templates/layers/next/typescript/package.json +14 -3
- package/templates/layers/next/typescript/src/components/common/Breadcrumbs.tsx +57 -8
- package/templates/layers/next/typescript/src/components/common/Loading.tsx +2 -1
- package/templates/layers/next/typescript/src/components/common/LottieIcon.tsx +2 -2
- package/templates/layers/next/typescript/src/components/common/Pagination.tsx +186 -0
- package/templates/layers/next/typescript/src/components/common/SidebarModal.tsx +200 -0
- package/templates/layers/next/typescript/src/components/common/StatsCard.tsx +232 -0
- package/templates/layers/next/typescript/src/components/common/Table.tsx +205 -39
- package/templates/layers/next/typescript/src/components/common/Tabs.tsx +190 -0
- package/templates/layers/next/typescript/src/components/common/ToggleSwitch.tsx +12 -8
- package/templates/layers/next/typescript/src/components/layout/PublicLayout.tsx +8 -6
- package/templates/layers/next/typescript/src/components/layout/SidebarLayout.tsx +8 -17
- package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +3 -1
- package/templates/layers/next/typescript/src/components/modals/Modal.tsx +67 -21
- package/templates/layers/next/typescript/src/components/ui/Button.tsx +1 -0
- package/templates/layers/next/typescript/src/components/ui/Header.tsx +2 -2
- package/templates/layers/next/typescript/src/components/ui/Input.tsx +40 -60
- package/templates/layers/next/{javascript/src/components/ui/sidebar.jsx → typescript/src/components/ui/Sidebar.tsx} +77 -44
- package/templates/layers/next/typescript/src/components/ui/SidebarHeader.tsx +16 -10
- package/templates/layers/next/typescript/src/config/appConfig.ts +4 -4
- package/templates/layers/next/typescript/src/hooks/usePwaInstall.ts +10 -4
- package/templates/layers/next/typescript/src/hooks/useWindowSize.ts +2 -3
- package/templates/layers/next/typescript/src/services/authService.ts +1 -1
- package/templates/layers/next/typescript/src/store/useAuthStore.ts +24 -9
- package/templates/layers/next/typescript/src/store/useUiStore.ts +14 -4
- package/templates/layers/node/javascript/.env.example +3 -1
- package/templates/layers/node/javascript/package.json +12 -3
- package/templates/layers/node/javascript/src/app.js +2 -1
- package/templates/layers/node/javascript/src/controllers/auth.controller.js +106 -168
- package/templates/layers/node/javascript/src/controllers/push.controller.js +51 -58
- package/templates/layers/node/javascript/src/controllers/upload.controller.js +12 -19
- package/templates/layers/node/javascript/src/middleware/auth.middleware.js +5 -11
- package/templates/layers/node/javascript/src/middleware/errorHandler.js +6 -4
- package/templates/layers/node/javascript/src/routes/auth.route.js +2 -0
- package/templates/layers/node/javascript/src/services/auth.service.js +159 -164
- package/templates/layers/node/javascript/src/utils/ApiError.js +20 -0
- package/templates/layers/node/javascript/src/utils/SuccessResponse.js +15 -0
- package/templates/layers/node/javascript/src/utils/apiResponse.js +4 -0
- package/templates/layers/node/javascript/src/utils/asyncHandler.js +5 -0
- package/templates/layers/node/typescript/.env.example +3 -1
- package/templates/layers/node/typescript/.eslintrc.cjs +28 -0
- package/templates/layers/node/typescript/package-lock.json +2 -2
- package/templates/layers/node/typescript/package.json +17 -3
- package/templates/layers/node/typescript/src/app.ts +3 -2
- package/templates/layers/node/typescript/src/config/db.ts +3 -2
- package/templates/layers/node/typescript/src/config/socket.ts +5 -4
- package/templates/layers/node/typescript/src/controllers/auth.controller.ts +104 -132
- package/templates/layers/node/typescript/src/controllers/push.controller.ts +52 -59
- package/templates/layers/node/typescript/src/controllers/upload.controller.ts +12 -19
- package/templates/layers/node/typescript/src/middleware/auth.middleware.ts +13 -7
- package/templates/layers/node/typescript/src/middleware/errorHandler.ts +18 -5
- package/templates/layers/node/typescript/src/middleware/rateLimiter.ts +2 -2
- package/templates/layers/node/typescript/src/routes/auth.route.ts +2 -0
- package/templates/layers/node/typescript/src/services/auth.service.ts +86 -43
- package/templates/layers/node/typescript/src/types/express.d.ts +5 -3
- package/templates/layers/node/typescript/src/utils/ApiError.ts +25 -0
- package/templates/layers/node/typescript/src/utils/SuccessResponse.ts +17 -0
- package/templates/layers/node/typescript/src/utils/apiResponse.ts +4 -0
- package/templates/layers/node/typescript/src/utils/asyncHandler.ts +13 -0
- package/templates/layers/node/typescript/src/utils/cloudinaryUpload.ts +18 -7
- package/templates/layers/node/typescript/src/utils/expoNotification.ts +1 -1
- package/templates/layers/node/typescript/src/utils/genOtpToken.ts +7 -1
- package/templates/layers/node/typescript/src/utils/jwt.ts +8 -2
- package/templates/layers/node/typescript/src/utils/logger.ts +2 -1
- package/templates/layers/node/typescript/src/utils/pushHelper.ts +13 -9
- package/templates/layers/node-prisma/javascript/.env.example +3 -1
- package/templates/layers/node-prisma/javascript/package.json +11 -2
- package/templates/layers/node-prisma/javascript/src/app.js +2 -1
- package/templates/layers/node-prisma/javascript/src/controllers/auth.controller.js +90 -135
- package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +64 -76
- package/templates/layers/node-prisma/javascript/src/controllers/upload.controller.js +12 -19
- package/templates/layers/node-prisma/javascript/src/middleware/auth.middleware.js +5 -4
- package/templates/layers/node-prisma/javascript/src/middleware/errorHandler.js +6 -4
- package/templates/layers/node-prisma/javascript/src/routes/auth.route.js +2 -0
- package/templates/layers/node-prisma/javascript/src/services/auth.service.js +145 -122
- package/templates/layers/node-prisma/javascript/src/utils/ApiError.js +20 -0
- package/templates/layers/node-prisma/javascript/src/utils/SuccessResponse.js +15 -0
- package/templates/layers/node-prisma/javascript/src/utils/apiResponse.js +4 -0
- package/templates/layers/node-prisma/javascript/src/utils/asyncHandler.js +5 -0
- package/templates/layers/node-prisma/typescript/.env.example +3 -1
- package/templates/layers/node-prisma/typescript/.eslintrc.cjs +28 -0
- package/templates/layers/node-prisma/typescript/package.json +19 -5
- package/templates/layers/node-prisma/typescript/src/app.ts +3 -2
- package/templates/layers/node-prisma/typescript/src/config/prisma.ts +3 -2
- package/templates/layers/node-prisma/typescript/src/config/socket.ts +5 -4
- package/templates/layers/node-prisma/typescript/src/controllers/auth.controller.ts +89 -105
- package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -83
- package/templates/layers/node-prisma/typescript/src/controllers/upload.controller.ts +12 -19
- package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +9 -13
- package/templates/layers/node-prisma/typescript/src/middleware/errorHandler.ts +18 -5
- package/templates/layers/node-prisma/typescript/src/middleware/rateLimiter.ts +2 -2
- package/templates/layers/node-prisma/typescript/src/routes/auth.route.ts +2 -0
- package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +83 -31
- package/templates/layers/node-prisma/typescript/src/types/express.d.ts +5 -3
- package/templates/layers/node-prisma/typescript/src/utils/ApiError.ts +25 -0
- package/templates/layers/node-prisma/typescript/src/utils/SuccessResponse.ts +17 -0
- package/templates/layers/node-prisma/typescript/src/utils/apiResponse.ts +4 -0
- package/templates/layers/node-prisma/typescript/src/utils/asyncHandler.ts +13 -0
- package/templates/layers/node-prisma/typescript/src/utils/cloudinaryUpload.ts +18 -7
- package/templates/layers/node-prisma/typescript/src/utils/genOtpToken.ts +7 -1
- package/templates/layers/node-prisma/typescript/src/utils/jwt.ts +8 -2
- package/templates/layers/node-prisma/typescript/src/utils/logger.ts +2 -1
- package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +10 -6
- package/templates/layers/prisma/javascript/package.json +8 -0
- package/templates/layers/prisma/typescript/package.json +14 -1
- package/templates/layers/prisma/typescript/prisma/schema.prisma +35 -10
- package/templates/layers/prisma/typescript/src/config/db.ts +3 -2
- package/templates/layers/react/javascript/package.json +6 -0
- package/templates/layers/react/javascript/src/components/common/Breadcrumbs.jsx +62 -58
- package/templates/layers/react/javascript/src/components/common/Pagination.jsx +164 -76
- package/templates/layers/react/javascript/src/components/common/SidebarModal.jsx +179 -0
- package/templates/layers/react/javascript/src/components/common/StatsCard.jsx +167 -0
- package/templates/layers/react/javascript/src/components/common/Table.jsx +248 -132
- package/templates/layers/react/javascript/src/components/common/Tabs.jsx +164 -112
- package/templates/layers/react/javascript/src/components/common/ToggleSwitch.jsx +3 -3
- package/templates/layers/react/javascript/src/components/layout/SidebarLayout.jsx +4 -12
- package/templates/layers/react/javascript/src/components/modals/Modal.jsx +53 -18
- package/templates/layers/react/javascript/src/components/ui/Input.jsx +49 -0
- package/templates/layers/react/javascript/src/components/ui/Sidebar.jsx +215 -0
- package/templates/layers/react/javascript/src/components/ui/SidebarHeader.jsx +25 -30
- package/templates/layers/react/javascript/src/config/appConfig.js +3 -3
- package/templates/layers/react/javascript/src/hooks/useWindowSize.js +2 -2
- package/templates/layers/react/typescript/.eslintrc.cjs +20 -0
- package/templates/layers/react/typescript/package.json +11 -2
- package/templates/layers/react/typescript/src/components/common/Breadcrumbs.tsx +32 -6
- package/templates/layers/react/typescript/src/components/common/Pagination.tsx +151 -57
- package/templates/layers/react/typescript/src/components/common/SidebarModal.tsx +199 -0
- package/templates/layers/react/typescript/src/components/common/StatsCard.tsx +231 -0
- package/templates/layers/react/typescript/src/components/common/Table.tsx +201 -38
- package/templates/layers/react/typescript/src/components/common/Tabs.tsx +56 -12
- package/templates/layers/react/typescript/src/components/common/ToggleSwitch.tsx +15 -3
- package/templates/layers/react/typescript/src/components/layout/SidebarLayout.tsx +3 -6
- package/templates/layers/react/typescript/src/components/modals/Modal.tsx +151 -0
- package/templates/layers/react/typescript/src/components/ui/Input.tsx +58 -0
- package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +116 -53
- package/templates/layers/react/typescript/src/components/ui/SidebarHeader.tsx +5 -4
- package/templates/layers/react/typescript/src/config/appConfig.ts +3 -3
- package/templates/layers/react/typescript/src/hooks/useWindowSize.ts +2 -3
- package/templates/layers/react/typescript/src/main.tsx +11 -11
- package/templates/layers/react/typescript/src/services/authService.ts +1 -1
- package/templates/layers/react/typescript/src/store/useAuthStore.ts +24 -9
- package/templates/layers/react-native/base/app.json +9 -22
- package/templates/layers/react-native/base/eslint.config.js +5 -1
- package/templates/layers/react-native/base/package.json +11 -1
- package/templates/layers/react-native/base/src/app/(auth)/edit-profile.tsx +7 -7
- package/templates/layers/react-native/base/src/app/(auth)/login.tsx +31 -7
- package/templates/layers/react-native/base/src/app/(tabs)/_layout.tsx +3 -3
- package/templates/layers/react-native/base/src/app/(tabs)/index.tsx +6 -5
- package/templates/layers/react-native/base/src/app/_layout.tsx +3 -3
- package/templates/layers/react-native/base/src/app/doc-viewer.tsx +3 -3
- package/templates/layers/react-native/base/src/components/common/ConfirmationToast.tsx +58 -40
- package/templates/layers/react-native/base/src/components/common/GoogleAuthButton.tsx +144 -0
- package/templates/layers/react-native/base/src/components/common/LocationLoader.tsx +64 -9
- package/templates/layers/react-native/base/src/components/common/Typography.tsx +54 -19
- package/templates/layers/react-native/base/src/components/ui/ClassicTabBar.tsx +15 -12
- package/templates/layers/react-native/base/src/components/ui/CustomTabBar.tsx +84 -90
- package/templates/layers/react-native/base/src/components/ui/Drawer.tsx +344 -0
- package/templates/layers/react-native/base/src/components/ui/Sidebar.tsx +4 -4
- package/templates/layers/react-native/base/src/components/ui/buttons/Button.tsx +29 -6
- package/templates/layers/react-native/base/src/components/ui/data-display/Accordion.tsx +2 -2
- package/templates/layers/react-native/base/src/components/ui/data-display/Badge.tsx +1 -2
- package/templates/layers/react-native/base/src/components/ui/data-display/Image.tsx +3 -3
- package/templates/layers/react-native/base/src/components/ui/data-display/SwipeableRow.tsx +28 -7
- package/templates/layers/react-native/base/src/components/ui/feedback/GestureWrapper.tsx +11 -4
- package/templates/layers/react-native/base/src/components/ui/inputs/DatePicker.tsx +3 -3
- package/templates/layers/react-native/base/src/components/ui/inputs/ImagePickerModal.tsx +45 -19
- package/templates/layers/react-native/base/src/components/ui/inputs/Input.tsx +28 -9
- package/templates/layers/react-native/base/src/components/ui/inputs/SearchablePicker.tsx +199 -0
- package/templates/layers/react-native/base/src/components/ui/inputs/Switch.tsx +7 -7
- package/templates/layers/react-native/base/src/components/ui/inputs/WebLocationPicker.tsx +146 -72
- package/templates/layers/react-native/base/src/config/api.ts +5 -5
- package/templates/layers/react-native/base/src/config/appConfig.ts +1 -1
- package/templates/layers/react-native/base/src/services/auth.service.ts +3 -1
- package/templates/layers/react-native/base/src/services/permission.service.ts +0 -2
- package/templates/layers/react-native/base/src/services/socket.service.ts +0 -7
- package/templates/layers/react-native/base/src/store/useAuthStore.ts +61 -16
- package/templates/layers/react-native/base/src/store/useNotificationStore.ts +36 -42
- package/templates/layers/react-native/base/src/types/images.d.ts +7 -5
- package/templates/layers/react-native/base/src/utils/fileDownload.ts +206 -0
- package/templates/layers/react-native/base/src/utils/storage.ts +1 -1
- package/templates/presets/electron/preset.json +9 -0
- package/templates/presets/electron-ts/preset.json +9 -0
- package/templates/presets/production-fullstack/overrides/client/package.json +9 -0
- package/templates/presets/production-fullstack/overrides/mobile/app.json +2 -15
- package/templates/presets/production-fullstack/overrides/mobile/package.json +11 -1
- package/templates/presets/production-fullstack/overrides/mobile/src/app/_layout.tsx +3 -3
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/Drawer.tsx +344 -0
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/DatePicker.tsx +3 -3
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/ImagePickerModal.tsx +3 -3
- package/templates/presets/production-fullstack/overrides/server/package.json +11 -2
- package/templates/layers/react/javascript/src/components/ui/sidebar.jsx +0 -169
- package/templates/layers/react-native/base/src/components/ui/BottomModal.tsx +0 -176
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/BottomModal.tsx +0 -159
- /package/templates/layers/react-native/base/assets/images/logo/{logoWithBg.png → icon.png} +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { Minus, Square, X, Maximize2, Minimize2 } from "lucide-react";
|
|
3
|
+
import { useUiStore } from "../../store/useUiStore";
|
|
4
|
+
import appConfig from "../../config/appConfig";
|
|
5
|
+
|
|
6
|
+
type Platform = "darwin" | "win32" | "linux" | "web";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* TitleBar — custom frameless window title bar for Electron.
|
|
10
|
+
*
|
|
11
|
+
* Renders a draggable title bar with:
|
|
12
|
+
* - App icon + name centered
|
|
13
|
+
* - Window controls (minimize / maximize / close) per platform:
|
|
14
|
+
* · macOS: right side (native traffic lights hidden via frame: false)
|
|
15
|
+
* · Windows/Linux: right side (Windows style)
|
|
16
|
+
*
|
|
17
|
+
* Only renders when running inside Electron (window.electronAPI present).
|
|
18
|
+
*/
|
|
19
|
+
const TitleBar: React.FC = () => {
|
|
20
|
+
const { windowMaximized, setWindowMaximized, minimizeWindow, maximizeWindow, closeWindow } =
|
|
21
|
+
useUiStore();
|
|
22
|
+
const [platform, setPlatform] = useState<Platform>("win32");
|
|
23
|
+
|
|
24
|
+
const isElectron = Boolean(window.electronAPI);
|
|
25
|
+
if (!isElectron) return null;
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
window.electronAPI.getPlatform().then((p) => setPlatform(p as Platform));
|
|
29
|
+
window.electronAPI.isMaximized().then(setWindowMaximized);
|
|
30
|
+
window.electronAPI.onMaximizeChange((isMax) => setWindowMaximized(isMax));
|
|
31
|
+
|
|
32
|
+
return () => {
|
|
33
|
+
window.electronAPI.removeAllListeners("window:maximized-change");
|
|
34
|
+
};
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const isMac = platform === "darwin";
|
|
38
|
+
|
|
39
|
+
const windowControls = (
|
|
40
|
+
<div className={`flex items-center gap-1.5 ${isMac ? "mr-3" : "ml-auto"}`}>
|
|
41
|
+
{/* Minimize */}
|
|
42
|
+
<button
|
|
43
|
+
id="titlebar-minimize"
|
|
44
|
+
onClick={minimizeWindow}
|
|
45
|
+
className="w-7 h-7 flex items-center justify-center rounded-full
|
|
46
|
+
text-base-content/50 hover:bg-warning/20 hover:text-warning
|
|
47
|
+
transition-all duration-150 group"
|
|
48
|
+
title="Minimize"
|
|
49
|
+
>
|
|
50
|
+
<Minus size={12} className="group-hover:scale-110 transition-transform" />
|
|
51
|
+
</button>
|
|
52
|
+
|
|
53
|
+
{/* Maximize / Restore */}
|
|
54
|
+
<button
|
|
55
|
+
id="titlebar-maximize"
|
|
56
|
+
onClick={maximizeWindow}
|
|
57
|
+
className="w-7 h-7 flex items-center justify-center rounded-full
|
|
58
|
+
text-base-content/50 hover:bg-success/20 hover:text-success
|
|
59
|
+
transition-all duration-150 group"
|
|
60
|
+
title={windowMaximized ? "Restore" : "Maximize"}
|
|
61
|
+
>
|
|
62
|
+
{windowMaximized ? (
|
|
63
|
+
<Minimize2 size={11} className="group-hover:scale-110 transition-transform" />
|
|
64
|
+
) : (
|
|
65
|
+
<Maximize2 size={11} className="group-hover:scale-110 transition-transform" />
|
|
66
|
+
)}
|
|
67
|
+
</button>
|
|
68
|
+
|
|
69
|
+
{/* Close */}
|
|
70
|
+
<button
|
|
71
|
+
id="titlebar-close"
|
|
72
|
+
onClick={closeWindow}
|
|
73
|
+
className="w-7 h-7 flex items-center justify-center rounded-full
|
|
74
|
+
text-base-content/50 hover:bg-error/20 hover:text-error
|
|
75
|
+
transition-all duration-150 group"
|
|
76
|
+
title="Close"
|
|
77
|
+
>
|
|
78
|
+
<X size={12} className="group-hover:scale-110 transition-transform" />
|
|
79
|
+
</button>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
id="electron-titlebar"
|
|
86
|
+
className="h-10 shrink-0 flex items-center px-3
|
|
87
|
+
bg-base-200 border-b border-base-300
|
|
88
|
+
select-none"
|
|
89
|
+
style={{ WebkitAppRegion: "drag" } as React.CSSProperties}
|
|
90
|
+
>
|
|
91
|
+
{isMac && (
|
|
92
|
+
<div style={{ WebkitAppRegion: "no-drag" } as React.CSSProperties}>
|
|
93
|
+
{windowControls}
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
{/* App name — centered */}
|
|
98
|
+
<div className="flex items-center gap-2 mx-auto">
|
|
99
|
+
{appConfig.app.logo ? (
|
|
100
|
+
<img src={appConfig.app.logo} alt="" className="w-4 h-4 rounded" />
|
|
101
|
+
) : (
|
|
102
|
+
<div className="w-4 h-4 rounded bg-primary flex items-center justify-center text-primary-content text-[8px] font-bold">
|
|
103
|
+
{appConfig.app.name.charAt(0)}
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
<span className="text-xs font-semibold text-base-content/70">
|
|
107
|
+
{appConfig.app.name}
|
|
108
|
+
</span>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{!isMac && (
|
|
112
|
+
<div style={{ WebkitAppRegion: "no-drag" } as React.CSSProperties}>
|
|
113
|
+
{windowControls}
|
|
114
|
+
</div>
|
|
115
|
+
)}
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export default TitleBar;
|
|
@@ -0,0 +1,52 @@
|
|
|
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 as string,
|
|
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,
|
|
48
|
+
},
|
|
49
|
+
} as const;
|
|
50
|
+
|
|
51
|
+
export type AppConfig = typeof appConfig;
|
|
52
|
+
export default appConfig;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
type Platform = "darwin" | "win32" | "linux" | "web";
|
|
4
|
+
|
|
5
|
+
interface UseElectronReturn {
|
|
6
|
+
isElectron: boolean;
|
|
7
|
+
minimize: () => void;
|
|
8
|
+
maximize: () => void;
|
|
9
|
+
close: () => void;
|
|
10
|
+
isMaximized: () => Promise<boolean>;
|
|
11
|
+
getVersion: () => Promise<string>;
|
|
12
|
+
getPlatform: () => Promise<Platform>;
|
|
13
|
+
openExternal: (url: string) => Promise<void>;
|
|
14
|
+
onMaximizeChange: (cb: (isMax: boolean) => void) => void;
|
|
15
|
+
onMenuAction: (cb: (data: { label: string }) => void) => void;
|
|
16
|
+
removeAllListeners: (channel: string) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* useElectron — safely access window.electronAPI in the renderer.
|
|
21
|
+
*
|
|
22
|
+
* Returns typed wrappers for all IPC calls. Every method is a no-op
|
|
23
|
+
* (or returns a resolved promise) when running outside of Electron.
|
|
24
|
+
*/
|
|
25
|
+
export function useElectron(): UseElectronReturn {
|
|
26
|
+
const isElectron = typeof window !== "undefined" && Boolean(window.electronAPI);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
isElectron,
|
|
30
|
+
minimize: () => window.electronAPI?.minimize(),
|
|
31
|
+
maximize: () => window.electronAPI?.maximize(),
|
|
32
|
+
close: () => window.electronAPI?.close(),
|
|
33
|
+
isMaximized: () => window.electronAPI?.isMaximized() ?? Promise.resolve(false),
|
|
34
|
+
getVersion: () => window.electronAPI?.getVersion() ?? Promise.resolve("web"),
|
|
35
|
+
getPlatform: () => (window.electronAPI?.getPlatform() ?? Promise.resolve("web")) as Promise<Platform>,
|
|
36
|
+
openExternal: (url) => window.electronAPI?.openExternal(url) ?? Promise.resolve(),
|
|
37
|
+
onMaximizeChange: (cb) => window.electronAPI?.onMaximizeChange(cb),
|
|
38
|
+
onMenuAction: (cb) => window.electronAPI?.onMenuAction(cb),
|
|
39
|
+
removeAllListeners: (channel) => window.electronAPI?.removeAllListeners(channel),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type ElectronEvent = "maximize" | "menu";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* useElectronEvent — subscribe to an Electron IPC event in a React component.
|
|
47
|
+
* Automatically cleans up the listener on unmount.
|
|
48
|
+
*/
|
|
49
|
+
export function useElectronEvent(
|
|
50
|
+
event: ElectronEvent,
|
|
51
|
+
callback: (value: boolean | { label: string }) => void,
|
|
52
|
+
): void {
|
|
53
|
+
const electron = useElectron();
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!electron.isElectron) return;
|
|
57
|
+
|
|
58
|
+
if (event === "maximize") {
|
|
59
|
+
electron.onMaximizeChange(callback as (v: boolean) => void);
|
|
60
|
+
return () => electron.removeAllListeners("window:maximized-change");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (event === "menu") {
|
|
64
|
+
electron.onMenuAction(callback as (d: { label: string }) => void);
|
|
65
|
+
return () => electron.removeAllListeners("menu:preferences");
|
|
66
|
+
}
|
|
67
|
+
}, [event, 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.js";
|
|
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,97 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
|
|
3
|
+
interface UiState {
|
|
4
|
+
// Sidebar
|
|
5
|
+
open: boolean;
|
|
6
|
+
collapsed: boolean;
|
|
7
|
+
isMobileMenuOpened: boolean;
|
|
8
|
+
|
|
9
|
+
// Modal
|
|
10
|
+
isModalOpen: boolean;
|
|
11
|
+
|
|
12
|
+
// Header
|
|
13
|
+
headerTitle: string;
|
|
14
|
+
headerActions: React.ReactNode | null;
|
|
15
|
+
breadcrumbs: React.ReactNode | null;
|
|
16
|
+
layoutClass: string;
|
|
17
|
+
|
|
18
|
+
// Theme
|
|
19
|
+
theme: string;
|
|
20
|
+
|
|
21
|
+
// Electron window
|
|
22
|
+
windowMaximized: boolean;
|
|
23
|
+
|
|
24
|
+
// Actions
|
|
25
|
+
setLayoutClass: (layoutClass: string) => void;
|
|
26
|
+
setHeaderTitle: (title: string) => void;
|
|
27
|
+
setHeaderActions: (actions: React.ReactNode | null) => void;
|
|
28
|
+
setBreadcrumbs: (breadcrumbs: React.ReactNode | null) => void;
|
|
29
|
+
toggleCollapsed: () => void;
|
|
30
|
+
setMobileMenuOpened: (open: boolean) => void;
|
|
31
|
+
toggleMobileMenuOpened: () => void;
|
|
32
|
+
toggleSidebar: () => void;
|
|
33
|
+
openSidebar: () => void;
|
|
34
|
+
close: () => void;
|
|
35
|
+
setModalOpen: (open: boolean) => void;
|
|
36
|
+
setTheme: (theme: string) => void;
|
|
37
|
+
toggleTheme: () => void;
|
|
38
|
+
|
|
39
|
+
// Electron window controls
|
|
40
|
+
setWindowMaximized: (value: boolean) => void;
|
|
41
|
+
minimizeWindow: () => void;
|
|
42
|
+
maximizeWindow: () => void;
|
|
43
|
+
closeWindow: () => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const useUiStore = create<UiState>((set, get) => ({
|
|
47
|
+
open: false,
|
|
48
|
+
isModalOpen: false,
|
|
49
|
+
collapsed: false,
|
|
50
|
+
isMobileMenuOpened: false,
|
|
51
|
+
headerTitle: "Dashboard",
|
|
52
|
+
headerActions: null,
|
|
53
|
+
breadcrumbs: null,
|
|
54
|
+
layoutClass: "",
|
|
55
|
+
|
|
56
|
+
// ── Layout helpers ──────────────────────────────────────────────────────────
|
|
57
|
+
setLayoutClass: (layoutClass) => set({ layoutClass }),
|
|
58
|
+
setHeaderTitle: (title) => set({ headerTitle: title }),
|
|
59
|
+
setHeaderActions: (actions) => set({ headerActions: actions }),
|
|
60
|
+
setBreadcrumbs: (breadcrumbs) => set({ breadcrumbs }),
|
|
61
|
+
|
|
62
|
+
// ── Sidebar ─────────────────────────────────────────────────────────────────
|
|
63
|
+
toggleCollapsed: () => set((state) => ({ collapsed: !state.collapsed })),
|
|
64
|
+
setMobileMenuOpened: (open) => set({ isMobileMenuOpened: open }),
|
|
65
|
+
toggleMobileMenuOpened: () =>
|
|
66
|
+
set((state) => ({ isMobileMenuOpened: !state.isMobileMenuOpened })),
|
|
67
|
+
toggleSidebar: () => set((state) => ({ open: !state.open })),
|
|
68
|
+
openSidebar: () => set({ open: true }),
|
|
69
|
+
close: () => set({ open: false }),
|
|
70
|
+
|
|
71
|
+
// ── Modal ───────────────────────────────────────────────────────────────────
|
|
72
|
+
setModalOpen: (open) => set({ isModalOpen: open }),
|
|
73
|
+
|
|
74
|
+
// ── Theme ───────────────────────────────────────────────────────────────────
|
|
75
|
+
theme: localStorage.getItem("theme") || "light",
|
|
76
|
+
setTheme: (theme) => {
|
|
77
|
+
document.documentElement.setAttribute("data-theme", theme);
|
|
78
|
+
localStorage.setItem("theme", theme);
|
|
79
|
+
set({ theme });
|
|
80
|
+
},
|
|
81
|
+
toggleTheme: () => {
|
|
82
|
+
const nextTheme = get().theme === "light" ? "dark" : "light";
|
|
83
|
+
document.documentElement.setAttribute("data-theme", nextTheme);
|
|
84
|
+
localStorage.setItem("theme", nextTheme);
|
|
85
|
+
set({ theme: nextTheme });
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// ── Electron window state ───────────────────────────────────────────────────
|
|
89
|
+
windowMaximized: false,
|
|
90
|
+
setWindowMaximized: (value) => set({ windowMaximized: value }),
|
|
91
|
+
minimizeWindow: () => window.electronAPI?.minimize(),
|
|
92
|
+
maximizeWindow: () => window.electronAPI?.maximize(),
|
|
93
|
+
closeWindow: () => window.electronAPI?.close(),
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
export { useUiStore };
|
|
97
|
+
export type { UiState };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
import type { ElectronAPI } from '../electron/preload';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Augment the Window interface to include the Electron context bridge API.
|
|
7
|
+
* This gives full type safety when accessing window.electronAPI in the renderer.
|
|
8
|
+
*/
|
|
9
|
+
declare global {
|
|
10
|
+
interface Window {
|
|
11
|
+
electronAPI: ElectronAPI;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
/* Bundler mode */
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
|
|
17
|
+
/* Linting */
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"noFallthroughCasesInSwitch": true
|
|
22
|
+
},
|
|
23
|
+
"include": ["src", "electron"],
|
|
24
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
25
|
+
}
|
|
@@ -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.ts',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
// Preload script — reload renderer on change
|
|
19
|
+
entry: 'electron/preload.ts',
|
|
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
|
+
});
|
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
"dev": "next dev",
|
|
7
7
|
"build": "next build",
|
|
8
8
|
"start": "next start",
|
|
9
|
-
"lint": "next lint"
|
|
9
|
+
"lint": "next lint",
|
|
10
|
+
"lint:fix": "next lint --fix",
|
|
11
|
+
"format": "prettier --write \"src/**/*.{js,jsx,css,json}\"",
|
|
12
|
+
"format:check": "prettier --check \"src/**/*.{js,jsx,css,json}\"",
|
|
13
|
+
"circular-check": "dpdm --circular --no-warning --no-tree --exit-code circular:1 \"src/**/*.{js,jsx}\""
|
|
10
14
|
},
|
|
11
15
|
"dependencies": {
|
|
12
16
|
"@ducanh2912/next-pwa": "latest",
|
|
@@ -27,7 +31,9 @@
|
|
|
27
31
|
"zustand": "^4.5.2"
|
|
28
32
|
},
|
|
29
33
|
"devDependencies": {
|
|
34
|
+
"dpdm": "^3.14.0",
|
|
30
35
|
"eslint": "^10.8.1",
|
|
31
|
-
"eslint-config-next": "^16.3.0"
|
|
36
|
+
"eslint-config-next": "^16.3.0",
|
|
37
|
+
"prettier": "^3.4.2"
|
|
32
38
|
}
|
|
33
39
|
}
|
|
@@ -1,25 +1,63 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import Link from "next/link";
|
|
4
|
-
import {
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { ArrowLeft, ChevronRight } from "lucide-react";
|
|
5
6
|
|
|
6
7
|
export const Breadcrumbs = ({ items }) => {
|
|
8
|
+
const router = useRouter();
|
|
7
9
|
return (
|
|
8
10
|
<div className="flex items-center gap-1.5 text-sm font-bold text-base-content/50 flex-wrap">
|
|
11
|
+
<button
|
|
12
|
+
className="btn btn-primary btn-soft btn-square btn-sm mr-1 hidden md:flex"
|
|
13
|
+
onClick={() => router.back()}
|
|
14
|
+
>
|
|
15
|
+
<ArrowLeft size={18} />
|
|
16
|
+
</button>
|
|
9
17
|
{items.map((item, idx) => {
|
|
10
18
|
const isLast = idx === items.length - 1;
|
|
11
19
|
return (
|
|
12
20
|
<React.Fragment key={idx}>
|
|
13
21
|
{item.path && !isLast ? (
|
|
14
|
-
<Link
|
|
22
|
+
<Link
|
|
23
|
+
href={item.path}
|
|
24
|
+
onClick={item.onClick}
|
|
25
|
+
className="hover:text-primary transition-colors hidden sm:flex items-center gap-1"
|
|
26
|
+
>
|
|
15
27
|
{item.label}
|
|
16
28
|
</Link>
|
|
17
|
-
) : (
|
|
18
|
-
<
|
|
29
|
+
) : item.onClick && !isLast ? (
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
onClick={item.onClick}
|
|
33
|
+
className="hover:text-primary transition-colors hidden sm:flex items-center gap-1"
|
|
34
|
+
>
|
|
19
35
|
{item.label}
|
|
20
|
-
</
|
|
36
|
+
</button>
|
|
37
|
+
) : (
|
|
38
|
+
<div className="flex flex-col">
|
|
39
|
+
<span
|
|
40
|
+
className={
|
|
41
|
+
isLast
|
|
42
|
+
? "text-lg md:text-xl font-bold leading-tight text-base-content tracking-tight truncate max-w-[200px] md:max-w-none"
|
|
43
|
+
: ""
|
|
44
|
+
}
|
|
45
|
+
>
|
|
46
|
+
{item.label}
|
|
47
|
+
</span>
|
|
48
|
+
{item.subtitle && (
|
|
49
|
+
<span className="text-xs text-base-content/60 font-normal truncate">
|
|
50
|
+
{item.subtitle}
|
|
51
|
+
</span>
|
|
52
|
+
)}
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
{!isLast && (
|
|
56
|
+
<ChevronRight
|
|
57
|
+
size={14}
|
|
58
|
+
className="text-base-content/30 shrink-0 hidden sm:block"
|
|
59
|
+
/>
|
|
21
60
|
)}
|
|
22
|
-
{!isLast && <ChevronRight size={14} className="text-base-content/30 shrink-0" />}
|
|
23
61
|
</React.Fragment>
|
|
24
62
|
);
|
|
25
63
|
})}
|