@imksh/create 1.0.30 → 1.0.31

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 (102) hide show
  1. package/package.json +1 -1
  2. package/src/utils/askTemplate.js +2 -1
  3. package/templates/layers/electron/base/electron/main.js +262 -0
  4. package/templates/layers/electron/base/electron/preload.js +37 -0
  5. package/templates/layers/electron/base/electron-builder.json5 +68 -0
  6. package/templates/layers/electron/javascript/_gitignore +38 -0
  7. package/templates/layers/electron/javascript/package.json +49 -0
  8. package/templates/layers/electron/javascript/src/components/layout/SidebarLayout.jsx +43 -0
  9. package/templates/layers/electron/javascript/src/components/ui/TitleBar.jsx +130 -0
  10. package/templates/layers/electron/javascript/src/config/appConfig.js +51 -0
  11. package/templates/layers/electron/javascript/src/hooks/useElectron.js +68 -0
  12. package/templates/layers/electron/javascript/src/main.jsx +17 -0
  13. package/templates/layers/electron/javascript/src/store/useUiStore.js +59 -0
  14. package/templates/layers/electron/javascript/vite.config.js +29 -0
  15. package/templates/layers/electron/typescript/_gitignore +38 -0
  16. package/templates/layers/electron/typescript/electron/main.ts +213 -0
  17. package/templates/layers/electron/typescript/electron/preload.ts +52 -0
  18. package/templates/layers/electron/typescript/package.json +52 -0
  19. package/templates/layers/electron/typescript/src/components/layout/SidebarLayout.tsx +42 -0
  20. package/templates/layers/electron/typescript/src/components/ui/TitleBar.tsx +120 -0
  21. package/templates/layers/electron/typescript/src/config/appConfig.ts +52 -0
  22. package/templates/layers/electron/typescript/src/hooks/useElectron.ts +68 -0
  23. package/templates/layers/electron/typescript/src/main.tsx +17 -0
  24. package/templates/layers/electron/typescript/src/store/useUiStore.ts +97 -0
  25. package/templates/layers/electron/typescript/src/vite-env.d.ts +13 -0
  26. package/templates/layers/electron/typescript/tsconfig.json +25 -0
  27. package/templates/layers/electron/typescript/tsconfig.node.json +11 -0
  28. package/templates/layers/electron/typescript/vite.config.ts +29 -0
  29. package/templates/layers/next/javascript/src/components/common/Breadcrumbs.jsx +44 -6
  30. package/templates/layers/next/javascript/src/components/common/Loading.jsx +1 -0
  31. package/templates/layers/next/javascript/src/components/common/LottieIcon.jsx +1 -0
  32. package/templates/layers/next/javascript/src/components/common/Pagination.jsx +170 -0
  33. package/templates/layers/next/javascript/src/components/common/SidebarModal.jsx +180 -0
  34. package/templates/layers/next/javascript/src/components/common/StatsCard.jsx +168 -0
  35. package/templates/layers/next/javascript/src/components/common/Table.jsx +191 -41
  36. package/templates/layers/next/javascript/src/components/common/Tabs.jsx +187 -0
  37. package/templates/layers/next/javascript/src/components/common/ToggleSwitch.jsx +4 -3
  38. package/templates/layers/next/javascript/src/components/layout/ProtectedRoute.jsx +2 -1
  39. package/templates/layers/next/javascript/src/components/layout/PublicLayout.jsx +3 -6
  40. package/templates/layers/next/javascript/src/components/layout/SidebarLayout.jsx +4 -17
  41. package/templates/layers/next/javascript/src/components/modals/ConfirmModal.jsx +4 -12
  42. package/templates/layers/next/javascript/src/components/modals/Modal.jsx +50 -19
  43. package/templates/layers/next/javascript/src/components/ui/Input.jsx +50 -0
  44. package/templates/layers/next/{typescript/src/components/ui/sidebar.tsx → javascript/src/components/ui/Sidebar.jsx} +65 -53
  45. package/templates/layers/next/javascript/src/components/ui/SidebarHeader.jsx +11 -6
  46. package/templates/layers/next/javascript/src/config/appConfig.js +4 -4
  47. package/templates/layers/next/javascript/src/hooks/useWindowSize.js +2 -3
  48. package/templates/layers/next/javascript/src/store/useUiStore.js +9 -4
  49. package/templates/layers/next/typescript/src/components/common/Breadcrumbs.tsx +57 -8
  50. package/templates/layers/next/typescript/src/components/common/Loading.tsx +1 -0
  51. package/templates/layers/next/typescript/src/components/common/Pagination.tsx +186 -0
  52. package/templates/layers/next/typescript/src/components/common/SidebarModal.tsx +200 -0
  53. package/templates/layers/next/typescript/src/components/common/StatsCard.tsx +232 -0
  54. package/templates/layers/next/typescript/src/components/common/Table.tsx +201 -35
  55. package/templates/layers/next/typescript/src/components/common/Tabs.tsx +190 -0
  56. package/templates/layers/next/typescript/src/components/common/ToggleSwitch.tsx +12 -8
  57. package/templates/layers/next/typescript/src/components/layout/PublicLayout.tsx +8 -6
  58. package/templates/layers/next/typescript/src/components/layout/SidebarLayout.tsx +8 -17
  59. package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +1 -0
  60. package/templates/layers/next/typescript/src/components/modals/Modal.tsx +67 -21
  61. package/templates/layers/next/typescript/src/components/ui/Button.tsx +1 -0
  62. package/templates/layers/next/typescript/src/components/ui/Input.tsx +40 -60
  63. package/templates/layers/next/{javascript/src/components/ui/sidebar.jsx → typescript/src/components/ui/Sidebar.tsx} +77 -44
  64. package/templates/layers/next/typescript/src/components/ui/SidebarHeader.tsx +16 -10
  65. package/templates/layers/next/typescript/src/config/appConfig.ts +4 -4
  66. package/templates/layers/next/typescript/src/hooks/useWindowSize.ts +2 -3
  67. package/templates/layers/next/typescript/src/store/useUiStore.ts +14 -4
  68. package/templates/layers/node/typescript/package-lock.json +2 -2
  69. package/templates/layers/node/typescript/package.json +1 -1
  70. package/templates/layers/prisma/typescript/prisma/schema.prisma +35 -10
  71. package/templates/layers/react/javascript/src/components/common/Breadcrumbs.jsx +62 -58
  72. package/templates/layers/react/javascript/src/components/common/Pagination.jsx +164 -76
  73. package/templates/layers/react/javascript/src/components/common/SidebarModal.jsx +179 -0
  74. package/templates/layers/react/javascript/src/components/common/StatsCard.jsx +167 -0
  75. package/templates/layers/react/javascript/src/components/common/Table.jsx +248 -132
  76. package/templates/layers/react/javascript/src/components/common/Tabs.jsx +164 -112
  77. package/templates/layers/react/javascript/src/components/common/ToggleSwitch.jsx +3 -3
  78. package/templates/layers/react/javascript/src/components/layout/SidebarLayout.jsx +4 -12
  79. package/templates/layers/react/javascript/src/components/modals/Modal.jsx +53 -18
  80. package/templates/layers/react/javascript/src/components/ui/Input.jsx +49 -0
  81. package/templates/layers/react/javascript/src/components/ui/Sidebar.jsx +215 -0
  82. package/templates/layers/react/javascript/src/components/ui/SidebarHeader.jsx +25 -30
  83. package/templates/layers/react/javascript/src/config/appConfig.js +3 -3
  84. package/templates/layers/react/javascript/src/hooks/useWindowSize.js +2 -2
  85. package/templates/layers/react/typescript/src/components/common/Breadcrumbs.tsx +32 -6
  86. package/templates/layers/react/typescript/src/components/common/Pagination.tsx +151 -57
  87. package/templates/layers/react/typescript/src/components/common/SidebarModal.tsx +199 -0
  88. package/templates/layers/react/typescript/src/components/common/StatsCard.tsx +231 -0
  89. package/templates/layers/react/typescript/src/components/common/Table.tsx +197 -34
  90. package/templates/layers/react/typescript/src/components/common/Tabs.tsx +56 -12
  91. package/templates/layers/react/typescript/src/components/common/ToggleSwitch.tsx +15 -3
  92. package/templates/layers/react/typescript/src/components/layout/SidebarLayout.tsx +3 -6
  93. package/templates/layers/react/typescript/src/components/modals/Modal.tsx +151 -0
  94. package/templates/layers/react/typescript/src/components/ui/Input.tsx +58 -0
  95. package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +116 -53
  96. package/templates/layers/react/typescript/src/components/ui/SidebarHeader.tsx +5 -4
  97. package/templates/layers/react/typescript/src/config/appConfig.ts +3 -3
  98. package/templates/layers/react/typescript/src/hooks/useWindowSize.ts +2 -3
  99. package/templates/layers/react-native/base/src/config/appConfig.ts +1 -1
  100. package/templates/presets/electron/preset.json +9 -0
  101. package/templates/presets/electron-ts/preset.json +9 -0
  102. package/templates/layers/react/javascript/src/components/ui/sidebar.jsx +0 -169
@@ -1,5 +1,6 @@
1
- import React from "react";
2
- import TableSkeleton from "../skeletons/common/TableSkeleton";
1
+ "use client";
2
+ import React, { useEffect, useRef } from "react";
3
+ import { Pagination } from "./Pagination";
3
4
 
4
5
  const Table = ({
5
6
  columns = [],
@@ -10,39 +11,126 @@ const Table = ({
10
11
  currentPage = 1,
11
12
  totalPages = 1,
12
13
  onPageChange = null,
14
+ wrapperClassName = "",
15
+ renderCard,
16
+ pagination = false,
17
+ header,
13
18
  }) => {
14
- if (loading) {
15
- return <TableSkeleton rows={5} columns={columns.length || 4} />;
16
- }
19
+ const observerTarget = useRef(null);
20
+
21
+ useEffect(() => {
22
+ if (!pagination || !onPageChange || currentPage >= totalPages) return;
23
+
24
+ const observer = new IntersectionObserver(
25
+ (entries) => {
26
+ if (entries[0].isIntersecting) {
27
+ onPageChange(currentPage + 1);
28
+ }
29
+ },
30
+ { threshold: 0.1 },
31
+ );
32
+
33
+ if (observerTarget.current) {
34
+ observer.observe(observerTarget.current);
35
+ }
36
+
37
+ return () => observer.disconnect();
38
+ }, [pagination, onPageChange, currentPage, totalPages]);
17
39
 
18
40
  return (
19
- <div className="flex flex-col w-full border border-base-300 rounded-xl overflow-hidden bg-base-100 shadow-sm">
20
- {/* Table Container */}
21
- <div className="overflow-x-auto w-full">
22
- <table className="table table-hover w-full min-w-full">
23
- {/* Header */}
24
- <thead className="bg-base-200/60 border-b border-base-300">
41
+ <div
42
+ className={`flex flex-col w-full h-full md:border md:border-base-300 rounded-xl overflow-hidden bg-base-100 ${wrapperClassName}`}
43
+ >
44
+ {/* Optional Custom Header Area */}
45
+ {header && (
46
+ <div className="shrink-0 bg-base-100 border-b border-base-300">
47
+ {header}
48
+ </div>
49
+ )}
50
+
51
+ {/* Desktop Table View (hidden on mobile if renderCard provided) */}
52
+ <div
53
+ className={`overflow-auto w-full flex-1 ${renderCard ? "hidden md:block" : "block"}`}
54
+ >
55
+ <table className="table table-hover w-full min-w-full relative border-collapse">
56
+ <thead className="bg-base-200 backdrop-blur sticky top-0 z-10 border-b border-base-300">
25
57
  <tr>
26
58
  {columns.map((col, index) => (
27
59
  <th
28
60
  key={col.accessor || index}
29
- className="px-6 py-4 text-left text-xs font-bold text-base-content/80 uppercase tracking-wider"
61
+ className={`px-6 py-4 text-left text-xs font-semibold text-base-content/70 uppercase tracking-wider bg-transparent border-none ${col.headerClass || ""}`}
30
62
  >
31
63
  {col.header}
32
64
  </th>
33
65
  ))}
34
66
  </tr>
35
67
  </thead>
68
+ <tbody className="divide-y divide-base-200/60 bg-base-100">
69
+ {loading ? (
70
+ Array.from({ length: 5 }).map((_, rowIndex) => (
71
+ <tr key={`skeleton-${rowIndex}`} className="animate-pulse">
72
+ {columns.map((col, colIndex) => {
73
+ const isRight = col.headerClass?.includes("text-right");
74
+ const isFirst = colIndex === 0;
75
+ const isLast = colIndex === columns.length - 1;
76
+ const headerText =
77
+ typeof col.header === "string"
78
+ ? col.header.toLowerCase()
79
+ : "";
36
80
 
37
- {/* Body */}
38
- <tbody className="divide-y divide-base-300 bg-base-100">
39
- {data.length > 0 ? (
81
+ return (
82
+ <td
83
+ key={colIndex}
84
+ className={`px-6 py-4 whitespace-nowrap align-middle border-none ${col.headerClass || ""}`}
85
+ >
86
+ {isRight ||
87
+ (isLast && headerText.includes("action")) ? (
88
+ <div className="flex justify-end items-center gap-2">
89
+ <div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
90
+ <div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
91
+ </div>
92
+ ) : headerText.includes("assigned") ? (
93
+ <div className="h-6 w-28 rounded-md bg-base-200 border border-base-300/80"></div>
94
+ ) : isFirst ? (
95
+ <div className="space-y-1.5">
96
+ <div
97
+ className="h-4 bg-base-300/80 rounded"
98
+ style={{
99
+ width: `${65 + (rowIndex % 3) * 15}%`,
100
+ maxWidth: "140px",
101
+ }}
102
+ ></div>
103
+ <div
104
+ className="h-3 bg-base-200 rounded"
105
+ style={{
106
+ width: `${40 + (rowIndex % 2) * 15}%`,
107
+ maxWidth: "90px",
108
+ }}
109
+ ></div>
110
+ </div>
111
+ ) : (
112
+ <div
113
+ className={`h-4 bg-base-300/70 rounded ${isRight ? "ml-auto" : ""}`}
114
+ style={{
115
+ width: `${50 + ((rowIndex + colIndex) % 4) * 12}%`,
116
+ maxWidth: "160px",
117
+ }}
118
+ ></div>
119
+ )}
120
+ </td>
121
+ );
122
+ })}
123
+ </tr>
124
+ ))
125
+ ) : data.length > 0 ? (
40
126
  data.map((row, rowIndex) => (
41
127
  <tr
42
128
  key={row.id || rowIndex}
43
129
  onClick={() => onRowClick && onRowClick(row)}
44
- className={`transition-colors duration-150 ${
45
- onRowClick ? "cursor-pointer hover:bg-base-200" : ""
130
+ className={`transition-all duration-200 group ${
131
+ onRowClick
132
+ ? "cursor-pointer hover:bg-base-200/30"
133
+ : "hover:bg-base-200/10"
46
134
  }`}
47
135
  >
48
136
  {columns.map((col, colIndex) => {
@@ -50,7 +138,7 @@ const Table = ({
50
138
  return (
51
139
  <td
52
140
  key={col.accessor || colIndex}
53
- className="px-6 py-4 text-sm text-base-content/90 whitespace-nowrap align-middle"
141
+ className="px-6 py-4 text-[13px] text-base-content/80 whitespace-nowrap align-middle border-none"
54
142
  >
55
143
  {col.render ? col.render(value, row, rowIndex) : value}
56
144
  </td>
@@ -75,36 +163,98 @@ const Table = ({
75
163
  </table>
76
164
  </div>
77
165
 
78
- {/* Pagination Footer */}
79
- {totalPages > 1 && onPageChange && (
80
- <div className="border-t border-base-300 px-6 py-4 bg-base-200/30 flex items-center justify-between shrink-0">
81
- {/* Info text */}
166
+ {/* Mobile Card View (only visible if renderCard provided) */}
167
+ {renderCard && (
168
+ <div className="md:hidden flex flex-col divide-y divide-base-200 overflow-y-auto flex-1">
169
+ {loading ? (
170
+ Array.from({ length: 4 }).map((_, i) => (
171
+ <div
172
+ key={`card-skeleton-${i}`}
173
+ className="p-4 flex flex-col gap-3 animate-pulse"
174
+ >
175
+ <div className="flex justify-between items-start">
176
+ <div className="space-y-1.5 flex-1">
177
+ <div className="h-4.5 bg-base-300/80 rounded w-36"></div>
178
+ <div className="h-3 bg-base-200 rounded w-24"></div>
179
+ </div>
180
+ <div className="flex gap-2">
181
+ <div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
182
+ <div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
183
+ </div>
184
+ </div>
185
+ <div className="space-y-2">
186
+ <div className="h-3.5 bg-base-200 rounded w-48"></div>
187
+ <div className="h-3.5 bg-base-200 rounded w-32"></div>
188
+ </div>
189
+ <div className="flex items-center justify-between pt-2 border-t border-base-200">
190
+ <div className="h-3 bg-base-200 rounded w-20"></div>
191
+ <div className="h-6 bg-base-300/80 rounded-lg w-28"></div>
192
+ </div>
193
+ </div>
194
+ ))
195
+ ) : data.length > 0 ? (
196
+ <>
197
+ {data.map((row, index) => (
198
+ <div
199
+ key={row.id || index}
200
+ onClick={() => onRowClick && onRowClick(row)}
201
+ className={onRowClick ? "cursor-pointer" : ""}
202
+ >
203
+ {renderCard(row, index)}
204
+ </div>
205
+ ))}
206
+ {pagination && currentPage < totalPages && (
207
+ <div ref={observerTarget} className="p-6 flex justify-center">
208
+ <span className="loading loading-spinner loading-sm text-primary"></span>
209
+ </div>
210
+ )}
211
+ </>
212
+ ) : (
213
+ <div className="p-12 text-center text-sm text-base-content/60 flex flex-col items-center gap-2">
214
+ <span className="text-2xl">📁</span>
215
+ <span>{emptyMessage}</span>
216
+ </div>
217
+ )}
218
+ </div>
219
+ )}
220
+
221
+ {/* Desktop Pagination / Load More Footer */}
222
+ {pagination && onPageChange && totalPages > 1 && (
223
+ <div
224
+ className={`border-t border-base-300 px-6 py-4 bg-base-200/30 flex items-center shrink-0 ${renderCard ? "hidden md:flex justify-between" : "justify-between"}`}
225
+ >
82
226
  <div className="text-xs text-base-content/75 font-medium">
83
- Page <span className="font-bold">{currentPage}</span> of{" "}
84
- <span className="font-bold">{totalPages}</span>
227
+ Showing <span className="font-bold">{data.length}</span> items
85
228
  </div>
86
-
87
- {/* Page controls */}
88
229
  <div className="flex gap-2">
89
- <button
90
- onClick={() => onPageChange(currentPage - 1)}
91
- disabled={currentPage <= 1}
92
- className="btn btn-sm btn-outline rounded-lg text-xs"
93
- >
94
- Previous
95
- </button>
96
- <button
97
- onClick={() => onPageChange(currentPage + 1)}
98
- disabled={currentPage >= totalPages}
99
- className="btn btn-sm btn-outline rounded-lg text-xs"
100
- >
101
- Next
102
- </button>
230
+ {currentPage < totalPages && (
231
+ <button
232
+ onClick={() => onPageChange(currentPage + 1)}
233
+ className="btn btn-sm btn-outline btn-primary rounded-lg text-xs"
234
+ >
235
+ Load More
236
+ </button>
237
+ )}
238
+ </div>
239
+ </div>
240
+ )}
241
+
242
+ {/* Standard Pagination (if pagination prop is not true) */}
243
+ {!pagination && onPageChange && totalPages > 0 && (
244
+ <div className="border-t border-base-300 px-3 sm:px-6 py-2.5 sm:py-4 bg-base-200/30 flex items-center justify-between shrink-0 mt-auto gap-2">
245
+ <div className="text-xs text-base-content/75 font-medium whitespace-nowrap">
246
+ Page <span className="font-bold">{currentPage}</span> of{" "}
247
+ <span className="font-bold">{totalPages}</span>
103
248
  </div>
249
+ <Pagination
250
+ currentPage={currentPage}
251
+ totalPages={totalPages}
252
+ onPageChange={onPageChange}
253
+ />
104
254
  </div>
105
255
  )}
106
256
  </div>
107
257
  );
108
258
  };
109
259
 
110
- export default Table;
260
+ export default Table;
@@ -0,0 +1,187 @@
1
+ "use client";
2
+ import React from "react";
3
+ import { ChevronDown } from "lucide-react";
4
+ import { motion } from "framer-motion";
5
+
6
+ export const Tabs = ({
7
+ tabs,
8
+ activeTab,
9
+ onChange,
10
+ variant = "box",
11
+ className = "bg-base-200 w-fit",
12
+ dropdown = false,
13
+ disabled = false,
14
+ size = "base",
15
+ }) => {
16
+ const [isOpen, setIsOpen] = React.useState(false);
17
+
18
+ const getTabSizeClass = () => {
19
+ switch (size) {
20
+ case "xs":
21
+ return "text-xs px-2.5 h-7 min-h-7";
22
+ case "sm":
23
+ return "text-sm px-3 h-8 min-h-8";
24
+ case "base":
25
+ return "text-base px-4 h-10 min-h-10";
26
+ case "lg":
27
+ return "text-lg px-6 h-12 min-h-12";
28
+ case "xl":
29
+ return "text-xl px-6 h-14 min-h-14";
30
+ case "2xl":
31
+ return "text-2xl px-8 h-16 min-h-16";
32
+ default:
33
+ return "text-base px-4 h-10 min-h-10";
34
+ }
35
+ };
36
+
37
+ const getBtnSizeClass = () => {
38
+ switch (size) {
39
+ case "xs":
40
+ return "btn-xs text-xs";
41
+ case "sm":
42
+ return "btn-sm text-sm";
43
+ case "base":
44
+ return "text-base";
45
+ case "lg":
46
+ return "btn-lg text-lg";
47
+ case "xl":
48
+ return "btn-lg text-xl";
49
+ case "2xl":
50
+ return "btn-lg text-2xl";
51
+ default:
52
+ return "text-base";
53
+ }
54
+ };
55
+
56
+ const tabSizeClass = getTabSizeClass();
57
+ const btnSizeClass = getBtnSizeClass();
58
+
59
+ // Close dropdown if user clicks outside
60
+ React.useEffect(() => {
61
+ if (!dropdown || !isOpen) return;
62
+ const closeMenu = () => setIsOpen(false);
63
+ document.addEventListener("click", closeMenu);
64
+ return () => document.removeEventListener("click", closeMenu);
65
+ }, [dropdown, isOpen]);
66
+
67
+ const renderDropdown = () => {
68
+ if (!dropdown) return null;
69
+ const activeItem = tabs.find((t) => t.id === activeTab);
70
+ return (
71
+ <div
72
+ className="relative md:hidden w-full max-w-xs z-[50]"
73
+ onClick={(e) => e.stopPropagation()}
74
+ >
75
+ {/* Trigger Button */}
76
+ <button
77
+ type="button"
78
+ onClick={() => setIsOpen(!isOpen)}
79
+ disabled={disabled}
80
+ className={`btn ${btnSizeClass} bg-base-200 border-base-300 border flex w-full items-center justify-between px-3 text-base-content ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
81
+ >
82
+ <div className="flex items-center gap-1.5">
83
+ {activeItem?.icon}
84
+ <span className="font-semibold">{activeItem?.label}</span>
85
+ </div>
86
+ <ChevronDown
87
+ size={14}
88
+ className={`opacity-50 transition-transform ${isOpen ? "rotate-180" : ""}`}
89
+ />
90
+ </button>
91
+
92
+ {/* Dropdown Menu */}
93
+ {isOpen && (
94
+ <div className="absolute top-full left-0 right-0 mt-1 bg-base-100 border border-base-200 shadow rounded-lg py-1 animate-in fade-in slide-in-from-top-2">
95
+ {tabs.map((tab) => (
96
+ <button
97
+ key={tab.id}
98
+ onClick={() => {
99
+ onChange(tab.id);
100
+ setIsOpen(false);
101
+ }}
102
+ type="button"
103
+ className={`w-full flex items-center gap-2 px-3 py-2 transition-colors ${size === "base" ? "text-sm" : `text-${size}`} ${
104
+ activeTab === tab.id
105
+ ? "bg-primary/10 text-primary font-semibold"
106
+ : "hover:bg-base-200 text-base-content/80"
107
+ }`}
108
+ >
109
+ {tab.icon}
110
+ {tab.label}
111
+ </button>
112
+ ))}
113
+ </div>
114
+ )}
115
+ </div>
116
+ );
117
+ };
118
+
119
+ if (variant === "box") {
120
+ return (
121
+ <>
122
+ {renderDropdown()}
123
+ <div
124
+ className={`tabs tabs-boxed p-1 flex-nowrap overflow-x-auto hide-scrollbar shrink-0 rounded-lg ${dropdown ? "hidden md:flex" : "flex"} ${className}`}
125
+ >
126
+ {tabs.map((tab) => (
127
+ <button
128
+ type="button"
129
+ key={tab.id}
130
+ onClick={() => onChange(tab.id)}
131
+ disabled={disabled}
132
+ className={`tab ${tabSizeClass} whitespace-nowrap flex items-center rounded-lg gap-2 relative z-0 ${
133
+ activeTab === tab.id
134
+ ? "text-primary-content"
135
+ : "text-base-content/70 hover:text-base-content "
136
+ } ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
137
+ >
138
+ {activeTab === tab.id && (
139
+ <motion.div
140
+ layoutId="activeTabBox"
141
+ className="absolute inset-0 bg-primary rounded-lg -z-10"
142
+ transition={{ type: "spring", duration: 0, bounce: 0.2 }}
143
+ />
144
+ )}
145
+ {tab.icon}
146
+ {tab.label}
147
+ </button>
148
+ ))}
149
+ </div>
150
+ </>
151
+ );
152
+ }
153
+
154
+ // default: underline
155
+ return (
156
+ <>
157
+ {renderDropdown()}
158
+ <div
159
+ className={`tabs tabs-bordered ${dropdown ? "hidden md:flex" : "flex"} ${className}`}
160
+ >
161
+ {tabs.map((tab) => (
162
+ <button
163
+ type="button"
164
+ key={tab.id}
165
+ onClick={() => onChange(tab.id)}
166
+ disabled={disabled}
167
+ className={`tab ${tabSizeClass} whitespace-nowrap relative !border-transparent ${
168
+ activeTab === tab.id
169
+ ? "font-semibold text-primary"
170
+ : "text-base-content/70 hover:text-base-content"
171
+ } ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
172
+ >
173
+ {activeTab === tab.id && (
174
+ <motion.div
175
+ layoutId="activeTabUnderline"
176
+ className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary"
177
+ transition={{ type: "spring", duration: 0.5, bounce: 0.2 }}
178
+ />
179
+ )}
180
+ {tab.icon && <span className="mr-2">{tab.icon}</span>}
181
+ {tab.label}
182
+ </button>
183
+ ))}
184
+ </div>
185
+ </>
186
+ );
187
+ };
@@ -1,10 +1,11 @@
1
+ "use client";
1
2
  import React from "react";
2
3
  import { motion } from "motion/react";
3
4
 
4
- const ToggleSwitch = ({ checked, onChange, label }) => {
5
+ const ToggleSwitch = ({ checked, onChange, label, className }) => {
5
6
  return (
6
7
  <div
7
- className="flex items-center gap-3 cursor-pointer select-none w-fit group"
8
+ className={`flex items-center gap-3 cursor-pointer select-none w-fit group ${className}`}
8
9
  onClick={() => onChange(!checked)}
9
10
  >
10
11
  <span className="text-sm font-bold">{label}</span>
@@ -16,7 +17,7 @@ const ToggleSwitch = ({ checked, onChange, label }) => {
16
17
  }`}
17
18
  >
18
19
  <motion.div
19
- className={`w-4 h-4 rounded-full shadow-sm ${
20
+ className={`w-4 h-4 rounded-full shadow ${
20
21
  checked ? "bg-white" : "bg-base-content/40"
21
22
  }`}
22
23
  layout
@@ -18,7 +18,8 @@ const ProtectedRoute = ({ children, role }) => {
18
18
  }
19
19
  }, [user, isCheckingAuth, role, router]);
20
20
 
21
- if (isCheckingAuth || !user || (role && user.role !== role)) return <Loading />;
21
+ if (isCheckingAuth || !user || (role && user.role !== role))
22
+ return <Loading />;
22
23
 
23
24
  return <>{children}</>;
24
25
  };
@@ -1,13 +1,10 @@
1
-
2
-
3
- import Header from "../ui/Header";
1
+ "use client";
4
2
 
5
3
  const PublicLayout = ({ children }) => {
6
4
  return (
7
- <>
8
- <Header />
5
+ <div className="min-h-screen bg-base-100 text-base-content">
9
6
  {children}
10
- </>
7
+ </div>
11
8
  );
12
9
  };
13
10
 
@@ -1,21 +1,11 @@
1
1
  "use client";
2
- import { useEffect } from "react";
3
- import { usePathname } from "next/navigation";
2
+ import { ReactNode } from "react";
4
3
  import SidebarHeader from "../ui/SidebarHeader";
5
- import Sidebar from "../ui/sidebar";
4
+ import Sidebar from "../ui/Sidebar";
6
5
  import { useUiStore } from "../../store/useUiStore";
7
- import appConfig from "../../config/appConfig";
8
6
 
9
7
  const SidebarLayout = ({ children }) => {
10
- const { headerTitle, headerActions, setHeaderTitle, setHeaderActions } =
11
- useUiStore();
12
- const pathname = usePathname();
13
-
14
- // Reset header title and actions whenever the route shifts
15
- useEffect(() => {
16
- setHeaderTitle(appConfig.app.name);
17
- setHeaderActions(null);
18
- }, [pathname, setHeaderTitle, setHeaderActions]);
8
+ const { headerTitle, headerActions } = useUiStore();
19
9
 
20
10
  return (
21
11
  <div className="flex h-screen overflow-hidden">
@@ -24,10 +14,7 @@ const SidebarLayout = ({ children }) => {
24
14
  <div className="flex flex-1 flex-col overflow-hidden">
25
15
  <SidebarHeader title={headerTitle} actions={headerActions} />
26
16
 
27
- <main
28
- className="flex-1 h-full overflow-y-auto px-2 py-6 md:p-6 max-w-7xl"
29
- data-lenis-prevent
30
- >
17
+ <main className="flex-1 h-full overflow-y-auto" data-lenis-prevent>
31
18
  {children}
32
19
  </main>
33
20
  </div>
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { useEffect } from "react";
2
3
  import { createPortal } from "react-dom";
3
4
  import {
@@ -63,7 +64,7 @@ const ConfirmModal = ({
63
64
 
64
65
  if (!open) return null;
65
66
 
66
- const style = variants[variant] || variants.warning;
67
+ const style = variants[variant || "warning"];
67
68
  const Icon = style.icon;
68
69
 
69
70
  return createPortal(
@@ -79,9 +80,7 @@ const ConfirmModal = ({
79
80
 
80
81
  <div className="relative w-full max-w-md mx-4 rounded-2xl bg-base-100 shadow-2xl animate-in fade-in zoom-in duration-200">
81
82
  <div className="p-6">
82
-
83
83
  <div className="flex gap-4">
84
-
85
84
  <div
86
85
  className={`w-14 h-14 rounded-full flex items-center justify-center shrink-0 ${style.iconBg}`}
87
86
  >
@@ -89,21 +88,15 @@ const ConfirmModal = ({
89
88
  </div>
90
89
 
91
90
  <div className="flex-1">
92
-
93
- <h2 className="text-xl font-bold">
94
- {title}
95
- </h2>
91
+ <h2 className="text-xl font-bold">{title}</h2>
96
92
 
97
93
  <p className="mt-2 text-sm text-base-content/70 leading-relaxed">
98
94
  {message}
99
95
  </p>
100
-
101
96
  </div>
102
-
103
97
  </div>
104
98
 
105
99
  <div className="mt-8 flex justify-end gap-3">
106
-
107
100
  <button
108
101
  className="btn btn-ghost"
109
102
  disabled={loading}
@@ -123,7 +116,6 @@ const ConfirmModal = ({
123
116
 
124
117
  {confirmText}
125
118
  </button>
126
-
127
119
  </div>
128
120
  </div>
129
121
  </div>
@@ -132,4 +124,4 @@ const ConfirmModal = ({
132
124
  );
133
125
  };
134
126
 
135
- export default ConfirmModal;
127
+ export default ConfirmModal;