@umituz/web-dashboard 2.0.7 → 2.0.8

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 (171) hide show
  1. package/package.json +35 -77
  2. package/src/domains/layouts/components/BrandLogo.tsx +83 -0
  3. package/src/domains/layouts/components/DashboardHeader.tsx +240 -0
  4. package/src/domains/layouts/components/DashboardLayout.tsx +155 -0
  5. package/src/domains/layouts/components/DashboardSidebar.tsx +152 -0
  6. package/src/domains/layouts/components/index.ts +8 -0
  7. package/src/domains/layouts/hooks/dashboard.ts +81 -0
  8. package/src/domains/layouts/hooks/index.ts +8 -0
  9. package/src/domains/layouts/index.ts +11 -0
  10. package/{dist/layouts/theme/default.js → src/domains/layouts/theme/default.ts} +18 -11
  11. package/src/domains/layouts/theme/index.ts +18 -0
  12. package/src/domains/layouts/theme/presets.ts +96 -0
  13. package/src/domains/layouts/theme/utils.ts +67 -0
  14. package/src/domains/layouts/types/index.ts +9 -0
  15. package/src/domains/layouts/types/layout.ts +43 -0
  16. package/src/domains/layouts/types/notification.ts +19 -0
  17. package/src/domains/layouts/types/sidebar.ts +35 -0
  18. package/src/domains/layouts/types/theme.ts +64 -0
  19. package/src/domains/layouts/types/user.ts +35 -0
  20. package/src/domains/layouts/utils/dashboard.ts +96 -0
  21. package/src/domains/layouts/utils/index.ts +11 -0
  22. package/src/domains/onboarding/components/AppFocusStep.tsx +113 -0
  23. package/src/domains/onboarding/components/OnboardingWizard.tsx +262 -0
  24. package/src/domains/onboarding/components/PlanStep.tsx +208 -0
  25. package/src/domains/onboarding/components/PlatformsStep.tsx +109 -0
  26. package/src/domains/onboarding/components/UserTypeStep.tsx +135 -0
  27. package/src/domains/onboarding/components/index.ts +9 -0
  28. package/src/domains/onboarding/hooks/index.ts +5 -0
  29. package/{dist/onboarding/hooks/index.js → src/domains/onboarding/hooks/useOnboarding.ts} +65 -19
  30. package/src/domains/onboarding/index.ts +35 -0
  31. package/src/domains/onboarding/types/index.ts +16 -0
  32. package/src/domains/onboarding/types/onboarding.ts +214 -0
  33. package/src/domains/onboarding/utils/index.ts +15 -0
  34. package/src/domains/onboarding/utils/onboarding.ts +166 -0
  35. package/src/domains/settings/components/SettingsLayout.tsx +144 -0
  36. package/src/domains/settings/components/SettingsSection.tsx +106 -0
  37. package/src/domains/settings/components/index.ts +6 -0
  38. package/src/domains/settings/hooks/index.ts +7 -0
  39. package/src/domains/settings/hooks/useSettings.ts +80 -0
  40. package/src/domains/settings/index.ts +22 -0
  41. package/src/domains/settings/types/index.ts +11 -0
  42. package/src/domains/settings/types/settings.ts +81 -0
  43. package/src/domains/settings/utils/index.ts +11 -0
  44. package/src/domains/settings/utils/settings.ts +80 -0
  45. package/dist/layouts/components/BrandLogo.d.ts +0 -18
  46. package/dist/layouts/components/BrandLogo.js +0 -88
  47. package/dist/layouts/components/BrandLogo.js.map +0 -1
  48. package/dist/layouts/components/DashboardHeader.d.ts +0 -36
  49. package/dist/layouts/components/DashboardHeader.js +0 -225
  50. package/dist/layouts/components/DashboardHeader.js.map +0 -1
  51. package/dist/layouts/components/DashboardLayout.d.ts +0 -45
  52. package/dist/layouts/components/DashboardLayout.js +0 -501
  53. package/dist/layouts/components/DashboardLayout.js.map +0 -1
  54. package/dist/layouts/components/DashboardSidebar.d.ts +0 -29
  55. package/dist/layouts/components/DashboardSidebar.js +0 -189
  56. package/dist/layouts/components/DashboardSidebar.js.map +0 -1
  57. package/dist/layouts/components/index.d.ts +0 -10
  58. package/dist/layouts/components/index.js +0 -502
  59. package/dist/layouts/components/index.js.map +0 -1
  60. package/dist/layouts/hooks/dashboard.d.ts +0 -35
  61. package/dist/layouts/hooks/dashboard.js +0 -57
  62. package/dist/layouts/hooks/dashboard.js.map +0 -1
  63. package/dist/layouts/hooks/index.d.ts +0 -3
  64. package/dist/layouts/hooks/index.js +0 -57
  65. package/dist/layouts/hooks/index.js.map +0 -1
  66. package/dist/layouts/index.d.ts +0 -17
  67. package/dist/layouts/index.js +0 -756
  68. package/dist/layouts/index.js.map +0 -1
  69. package/dist/layouts/theme/default.d.ts +0 -18
  70. package/dist/layouts/theme/default.js.map +0 -1
  71. package/dist/layouts/theme/index.d.ts +0 -4
  72. package/dist/layouts/theme/index.js +0 -184
  73. package/dist/layouts/theme/index.js.map +0 -1
  74. package/dist/layouts/theme/presets.d.ts +0 -14
  75. package/dist/layouts/theme/presets.js +0 -137
  76. package/dist/layouts/theme/presets.js.map +0 -1
  77. package/dist/layouts/theme/utils.d.ts +0 -22
  78. package/dist/layouts/theme/utils.js +0 -181
  79. package/dist/layouts/theme/utils.js.map +0 -1
  80. package/dist/layouts/types/index.d.ts +0 -6
  81. package/dist/layouts/types/index.js +0 -2
  82. package/dist/layouts/types/index.js.map +0 -1
  83. package/dist/layouts/types/layout.d.ts +0 -45
  84. package/dist/layouts/types/layout.js +0 -2
  85. package/dist/layouts/types/layout.js.map +0 -1
  86. package/dist/layouts/types/notification.d.ts +0 -20
  87. package/dist/layouts/types/notification.js +0 -2
  88. package/dist/layouts/types/notification.js.map +0 -1
  89. package/dist/layouts/types/sidebar.d.ts +0 -36
  90. package/dist/layouts/types/sidebar.js +0 -2
  91. package/dist/layouts/types/sidebar.js.map +0 -1
  92. package/dist/layouts/types/theme.d.ts +0 -64
  93. package/dist/layouts/types/theme.js +0 -2
  94. package/dist/layouts/types/theme.js.map +0 -1
  95. package/dist/layouts/types/user.d.ts +0 -37
  96. package/dist/layouts/types/user.js +0 -2
  97. package/dist/layouts/types/user.js.map +0 -1
  98. package/dist/layouts/utils/dashboard.d.ts +0 -57
  99. package/dist/layouts/utils/dashboard.js +0 -44
  100. package/dist/layouts/utils/dashboard.js.map +0 -1
  101. package/dist/layouts/utils/index.d.ts +0 -1
  102. package/dist/layouts/utils/index.js +0 -44
  103. package/dist/layouts/utils/index.js.map +0 -1
  104. package/dist/onboarding/components/AppFocusStep.d.ts +0 -26
  105. package/dist/onboarding/components/AppFocusStep.js +0 -86
  106. package/dist/onboarding/components/AppFocusStep.js.map +0 -1
  107. package/dist/onboarding/components/OnboardingWizard.d.ts +0 -13
  108. package/dist/onboarding/components/OnboardingWizard.js +0 -332
  109. package/dist/onboarding/components/OnboardingWizard.js.map +0 -1
  110. package/dist/onboarding/components/PlanStep.d.ts +0 -21
  111. package/dist/onboarding/components/PlanStep.js +0 -167
  112. package/dist/onboarding/components/PlanStep.js.map +0 -1
  113. package/dist/onboarding/components/PlatformsStep.d.ts +0 -26
  114. package/dist/onboarding/components/PlatformsStep.js +0 -86
  115. package/dist/onboarding/components/PlatformsStep.js.map +0 -1
  116. package/dist/onboarding/components/UserTypeStep.d.ts +0 -30
  117. package/dist/onboarding/components/UserTypeStep.js +0 -93
  118. package/dist/onboarding/components/UserTypeStep.js.map +0 -1
  119. package/dist/onboarding/components/index.d.ts +0 -9
  120. package/dist/onboarding/components/index.js +0 -738
  121. package/dist/onboarding/components/index.js.map +0 -1
  122. package/dist/onboarding/hooks/index.d.ts +0 -4
  123. package/dist/onboarding/hooks/index.js.map +0 -1
  124. package/dist/onboarding/hooks/useOnboarding.d.ts +0 -50
  125. package/dist/onboarding/hooks/useOnboarding.js +0 -100
  126. package/dist/onboarding/hooks/useOnboarding.js.map +0 -1
  127. package/dist/onboarding/index.d.ts +0 -11
  128. package/dist/onboarding/index.js +0 -913
  129. package/dist/onboarding/index.js.map +0 -1
  130. package/dist/onboarding/types/index.d.ts +0 -3
  131. package/dist/onboarding/types/index.js +0 -2
  132. package/dist/onboarding/types/index.js.map +0 -1
  133. package/dist/onboarding/types/onboarding.d.ts +0 -209
  134. package/dist/onboarding/types/onboarding.js +0 -2
  135. package/dist/onboarding/types/onboarding.js.map +0 -1
  136. package/dist/onboarding/utils/index.d.ts +0 -4
  137. package/dist/onboarding/utils/index.js +0 -83
  138. package/dist/onboarding/utils/index.js.map +0 -1
  139. package/dist/onboarding/utils/onboarding.d.ts +0 -106
  140. package/dist/onboarding/utils/onboarding.js +0 -83
  141. package/dist/onboarding/utils/onboarding.js.map +0 -1
  142. package/dist/settings/components/SettingsLayout.d.ts +0 -19
  143. package/dist/settings/components/SettingsLayout.js +0 -170
  144. package/dist/settings/components/SettingsLayout.js.map +0 -1
  145. package/dist/settings/components/SettingsSection.d.ts +0 -24
  146. package/dist/settings/components/SettingsSection.js +0 -73
  147. package/dist/settings/components/SettingsSection.js.map +0 -1
  148. package/dist/settings/components/index.d.ts +0 -5
  149. package/dist/settings/components/index.js +0 -169
  150. package/dist/settings/components/index.js.map +0 -1
  151. package/dist/settings/hooks/index.d.ts +0 -3
  152. package/dist/settings/hooks/index.js +0 -59
  153. package/dist/settings/hooks/index.js.map +0 -1
  154. package/dist/settings/hooks/useSettings.d.ts +0 -25
  155. package/dist/settings/hooks/useSettings.js +0 -59
  156. package/dist/settings/hooks/useSettings.js.map +0 -1
  157. package/dist/settings/index.d.ts +0 -7
  158. package/dist/settings/index.js +0 -259
  159. package/dist/settings/index.js.map +0 -1
  160. package/dist/settings/types/index.d.ts +0 -2
  161. package/dist/settings/types/index.js +0 -2
  162. package/dist/settings/types/index.js.map +0 -1
  163. package/dist/settings/types/settings.d.ts +0 -79
  164. package/dist/settings/types/settings.js +0 -2
  165. package/dist/settings/types/settings.js.map +0 -1
  166. package/dist/settings/utils/index.d.ts +0 -3
  167. package/dist/settings/utils/index.js +0 -39
  168. package/dist/settings/utils/index.js.map +0 -1
  169. package/dist/settings/utils/settings.d.ts +0 -50
  170. package/dist/settings/utils/settings.js +0 -39
  171. package/dist/settings/utils/settings.js.map +0 -1
package/package.json CHANGED
@@ -1,89 +1,45 @@
1
1
  {
2
2
  "name": "@umituz/web-dashboard",
3
- "version": "2.0.7",
3
+ "version": "2.0.8",
4
4
  "description": "Dashboard Layout System - Customizable, themeable dashboard layouts and settings",
5
- "type": "module",
5
+ "main": "./src/index.ts",
6
+ "types": "./src/index.ts",
7
+ "sideEffects": false,
6
8
  "exports": {
7
- "./layouts": {
8
- "types": "./dist/layouts/index.d.ts",
9
- "import": "./dist/layouts/index.js"
10
- },
11
- "./layouts/components": {
12
- "types": "./dist/layouts/components/index.d.ts",
13
- "import": "./dist/layouts/components/index.js"
14
- },
15
- "./layouts/hooks": {
16
- "types": "./dist/layouts/hooks/index.d.ts",
17
- "import": "./dist/layouts/hooks/index.js"
18
- },
19
- "./layouts/utils": {
20
- "types": "./dist/layouts/utils/index.d.ts",
21
- "import": "./dist/layouts/utils/index.js"
22
- },
23
- "./layouts/types": {
24
- "types": "./dist/layouts/types/index.d.ts",
25
- "import": "./dist/layouts/types/index.js"
26
- },
27
- "./layouts/theme": {
28
- "types": "./dist/layouts/theme/index.d.ts",
29
- "import": "./dist/layouts/theme/index.js"
30
- },
31
- "./settings": {
32
- "types": "./dist/settings/index.d.ts",
33
- "import": "./dist/settings/index.js"
34
- },
35
- "./settings/components": {
36
- "types": "./dist/settings/components/index.d.ts",
37
- "import": "./dist/settings/components/index.js"
38
- },
39
- "./settings/hooks": {
40
- "types": "./dist/settings/hooks/index.d.ts",
41
- "import": "./dist/settings/hooks/index.js"
42
- },
43
- "./settings/utils": {
44
- "types": "./dist/settings/utils/index.d.ts",
45
- "import": "./dist/settings/utils/index.js"
46
- },
47
- "./settings/types": {
48
- "types": "./dist/settings/types/index.d.ts",
49
- "import": "./dist/settings/types/index.js"
50
- },
51
- "./onboarding": {
52
- "types": "./dist/onboarding/index.d.ts",
53
- "import": "./dist/onboarding/index.js"
54
- },
55
- "./onboarding/components": {
56
- "types": "./dist/onboarding/components/index.d.ts",
57
- "import": "./dist/onboarding/components/index.js"
58
- },
59
- "./onboarding/hooks": {
60
- "types": "./dist/onboarding/hooks/index.d.ts",
61
- "import": "./dist/onboarding/hooks/index.js"
62
- },
63
- "./onboarding/utils": {
64
- "types": "./dist/onboarding/utils/index.d.ts",
65
- "import": "./dist/onboarding/utils/index.js"
66
- },
67
- "./onboarding/types": {
68
- "types": "./dist/onboarding/types/index.d.ts",
69
- "import": "./dist/onboarding/types/index.js"
70
- },
9
+ ".": "./src/index.ts",
10
+ "./layouts": "./src/domains/layouts/index.ts",
11
+ "./layouts/components": "./src/domains/layouts/components/index.ts",
12
+ "./layouts/hooks": "./src/domains/layouts/hooks/index.ts",
13
+ "./layouts/utils": "./src/domains/layouts/utils/index.ts",
14
+ "./layouts/types": "./src/domains/layouts/types/index.ts",
15
+ "./layouts/theme": "./src/domains/layouts/theme/index.ts",
16
+ "./settings": "./src/domains/settings/index.ts",
17
+ "./settings/components": "./src/domains/settings/components/index.ts",
18
+ "./settings/hooks": "./src/domains/settings/hooks/index.ts",
19
+ "./settings/utils": "./src/domains/settings/utils/index.ts",
20
+ "./settings/types": "./src/domains/settings/types/index.ts",
21
+ "./onboarding": "./src/domains/onboarding/index.ts",
22
+ "./onboarding/components": "./src/domains/onboarding/components/index.ts",
23
+ "./onboarding/hooks": "./src/domains/onboarding/hooks/index.ts",
24
+ "./onboarding/utils": "./src/domains/onboarding/utils/index.ts",
25
+ "./onboarding/types": "./src/domains/onboarding/types/index.ts",
71
26
  "./package.json": "./package.json"
72
27
  },
73
28
  "files": [
74
- "dist",
29
+ "src",
75
30
  "README.md"
76
31
  ],
77
32
  "scripts": {
78
- "build": "tsup",
79
- "dev": "tsup --watch",
33
+ "typecheck": "tsc --noEmit",
80
34
  "lint": "eslint src",
81
- "test": "vitest",
82
- "prepublishOnly": "npm run build"
35
+ "version:patch": "npm version patch -m 'chore: release v%s'",
36
+ "version:minor": "npm version minor -m 'chore: release v%s'",
37
+ "version:major": "npm version major -m 'chore: release v%s'"
83
38
  },
84
39
  "peerDependencies": {
85
40
  "react": "^18.0.0 || ^19.0.0",
86
- "react-dom": "^18.0.0 || ^19.0.0"
41
+ "react-dom": "^18.0.0 || ^19.0.0",
42
+ "react-router-dom": "^7.0.0"
87
43
  },
88
44
  "dependencies": {
89
45
  "class-variance-authority": "^0.7.1",
@@ -100,9 +56,7 @@
100
56
  "eslint": "^10.0.2",
101
57
  "react-i18next": "^16.5.4",
102
58
  "react-router-dom": "^7.13.1",
103
- "tsup": "^8.4.0",
104
- "typescript": "^5.9.3",
105
- "vitest": "^4.0.18"
59
+ "typescript": "^5.9.3"
106
60
  },
107
61
  "keywords": [
108
62
  "dashboard",
@@ -116,10 +70,14 @@
116
70
  "admin",
117
71
  "sidebar",
118
72
  "header",
119
- "config-driven"
73
+ "config-driven",
74
+ "package-driven"
120
75
  ],
121
- "author": "Umit Uz",
76
+ "author": "umituz",
122
77
  "license": "MIT",
78
+ "publishConfig": {
79
+ "access": "public"
80
+ },
123
81
  "repository": {
124
82
  "type": "git",
125
83
  "url": "git+https://github.com/umituz/web-dashboard.git"
@@ -0,0 +1,83 @@
1
+ import React from "react";
2
+ import { cn } from "@umituz/web-design-system/utils";
3
+
4
+ interface BrandLogoProps {
5
+ className?: string;
6
+ size?: number;
7
+ }
8
+
9
+ /**
10
+ * BrandLogo Component
11
+ *
12
+ * Displays the application brand logo as an SVG.
13
+ * Supports custom sizing and styling through className.
14
+ *
15
+ * @param className - Optional CSS classes for styling
16
+ * @param size - Width and height in pixels (default: 32)
17
+ */
18
+ export const BrandLogo = ({ className, size = 32 }: BrandLogoProps) => {
19
+ return (
20
+ <svg
21
+ width={size}
22
+ height={size}
23
+ viewBox="0 0 100 100"
24
+ fill="none"
25
+ xmlns="http://www.w3.org/2000/svg"
26
+ className={cn("shrink-0", className)}
27
+ >
28
+ {/* Solid Foundation / Platform */}
29
+ <rect
30
+ x="15"
31
+ y="65"
32
+ width="70"
33
+ height="15"
34
+ rx="4"
35
+ fill="hsl(var(--primary))"
36
+ />
37
+
38
+ {/* Assembly Paths / Structure */}
39
+ <rect
40
+ x="25"
41
+ y="25"
42
+ width="12"
43
+ height="40"
44
+ rx="2"
45
+ fill="hsl(var(--primary))"
46
+ />
47
+ <rect
48
+ x="44"
49
+ y="35"
50
+ width="12"
51
+ height="30"
52
+ rx="2"
53
+ fill="hsl(var(--primary))"
54
+ />
55
+ <rect
56
+ x="63"
57
+ y="20"
58
+ width="12"
59
+ height="45"
60
+ rx="2"
61
+ fill="hsl(var(--primary))"
62
+ />
63
+
64
+ {/* Modern Accent - The 'Assembly' Bridge */}
65
+ <rect
66
+ x="20"
67
+ y="45"
68
+ width="60"
69
+ height="10"
70
+ rx="2"
71
+ fill="hsl(var(--secondary))"
72
+ />
73
+
74
+ {/* Connection Point / Beacon */}
75
+ <circle
76
+ cx="50"
77
+ cy="20"
78
+ r="5"
79
+ fill="hsl(var(--secondary))"
80
+ />
81
+ </svg>
82
+ );
83
+ };
@@ -0,0 +1,240 @@
1
+ import React, { useState } from "react";
2
+ import {
3
+ Bell, X, Sun, Moon, Menu, User, Settings, LogOut,
4
+ ChevronDown, CreditCard
5
+ } from "lucide-react";
6
+ import { Button } from "@umituz/web-design-system/atoms";
7
+ import { useNavigate } from "react-router-dom";
8
+ import { useTranslation } from "react-i18next";
9
+ import type { DashboardHeaderProps } from "../types/layout";
10
+ import type { DashboardNotification } from "../types/notification";
11
+ import type { DashboardUser } from "../types/user";
12
+ import { formatNotificationTime } from "../utils/dashboard";
13
+
14
+ interface DashboardHeaderPropsExtended extends DashboardHeaderProps {
15
+ /** Auth user */
16
+ user?: DashboardUser;
17
+ /** Notifications */
18
+ notifications?: DashboardNotification[];
19
+ /** Logout function */
20
+ onLogout?: () => Promise<void>;
21
+ /** Mark all as read function */
22
+ onMarkAllRead?: () => void;
23
+ /** Dismiss notification function */
24
+ onDismissNotification?: (id: string) => void;
25
+ /** Settings route */
26
+ settingsRoute?: string;
27
+ /** Profile route */
28
+ profileRoute?: string;
29
+ /** Billing route */
30
+ billingRoute?: string;
31
+ }
32
+
33
+ /**
34
+ * Dashboard Header Component
35
+ *
36
+ * Displays top navigation bar with theme toggle, notifications,
37
+ * user menu, and organisation selector.
38
+ *
39
+ * @param props - Dashboard header props
40
+ */
41
+ export const DashboardHeader = ({
42
+ collapsed,
43
+ setCollapsed,
44
+ setMobileOpen,
45
+ title,
46
+ user,
47
+ notifications = [],
48
+ onLogout,
49
+ onMarkAllRead,
50
+ onDismissNotification,
51
+ settingsRoute = "/dashboard/settings",
52
+ profileRoute = "/dashboard/profile",
53
+ billingRoute = "/dashboard/billing",
54
+ }: DashboardHeaderPropsExtended) => {
55
+ const navigate = useNavigate();
56
+ const { t } = useTranslation();
57
+ const [notifOpen, setNotifOpen] = useState(false);
58
+ const [profileOpen, setProfileOpen] = useState(false);
59
+
60
+ const unreadCount = notifications.filter((n) => !n.read).length;
61
+
62
+ const markAllRead = () => {
63
+ onMarkAllRead?.();
64
+ };
65
+
66
+ const handleLogout = async () => {
67
+ try {
68
+ await onLogout?.();
69
+ navigate("/login");
70
+ } catch {
71
+ // Error handling can be added by parent component
72
+ }
73
+ };
74
+
75
+ // Placeholder components - these should be provided by the consuming app
76
+ const ThemeToggle = () => {
77
+ const [resolvedTheme, setResolvedTheme] = React.useState<"light" | "dark">("light");
78
+
79
+ return (
80
+ <Button
81
+ variant="ghost"
82
+ size="icon"
83
+ onClick={() => setResolvedTheme(resolvedTheme === "light" ? "dark" : "light")}
84
+ className="text-muted-foreground h-9 w-9"
85
+ title={resolvedTheme === "dark" ? t('common.tooltips.switchLight') : t('common.tooltips.switchDark')}
86
+ >
87
+ {resolvedTheme === "dark" ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
88
+ </Button>
89
+ );
90
+ };
91
+
92
+ return (
93
+ <header className="flex h-14 items-center justify-between border-b border-border bg-card/50 backdrop-blur-md px-4 shrink-0 z-30">
94
+ <div className="flex items-center gap-3">
95
+ <Button variant="ghost" size="icon" className="md:hidden" onClick={() => setMobileOpen(true)}>
96
+ <Menu className="h-5 w-5" />
97
+ </Button>
98
+ {collapsed && (
99
+ <Button variant="ghost" size="icon" className="hidden md:inline-flex" onClick={() => setCollapsed(false)}>
100
+ <Menu className="h-5 w-5" />
101
+ </Button>
102
+ )}
103
+ <h2 className="text-sm font-semibold text-foreground">
104
+ {title}
105
+ </h2>
106
+ </div>
107
+
108
+ <div className="flex items-center gap-2">
109
+ {/* Theme Toggle */}
110
+ <ThemeToggle />
111
+
112
+ {/* Notifications */}
113
+ <div className="relative">
114
+ <Button
115
+ variant="ghost"
116
+ size="icon"
117
+ className="text-muted-foreground relative h-9 w-9"
118
+ onClick={() => {
119
+ setNotifOpen(!notifOpen);
120
+ setProfileOpen(false);
121
+ }}
122
+ >
123
+ <Bell className="h-4 w-4" />
124
+ {unreadCount > 0 && (
125
+ <span className="absolute top-2 right-2 flex h-2 w-2">
126
+ <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-destructive opacity-75"></span>
127
+ <span className="relative inline-flex rounded-full h-2 w-2 bg-destructive"></span>
128
+ </span>
129
+ )}
130
+ </Button>
131
+
132
+ {notifOpen && (
133
+ <>
134
+ <div className="fixed inset-0 z-40" onClick={() => setNotifOpen(false)} />
135
+ <div className="absolute top-12 right-0 w-80 bg-popover border border-border rounded-xl shadow-xl z-50 overflow-hidden animate-in fade-in zoom-in-95 duration-200">
136
+ <div className="flex items-center justify-between px-4 py-3 border-b border-border bg-muted/50">
137
+ <h3 className="text-xs font-bold uppercase tracking-wider text-foreground">{t('dashboard.notifications.title')}</h3>
138
+ {unreadCount > 0 && (
139
+ <button onClick={markAllRead} className="text-[10px] font-bold text-primary hover:underline uppercase">{t('dashboard.notifications.markAllRead')}</button>
140
+ )}
141
+ </div>
142
+
143
+ <div className="max-h-[400px] overflow-y-auto">
144
+ {notifications.map((n) => (
145
+ <div key={n.id} className={`px-4 py-3 border-b border-border last:border-0 flex items-start gap-3 transition-colors hover:bg-muted/30 ${!n.read ? "bg-primary/5" : ""}`}>
146
+ <div className="flex-1 min-w-0">
147
+ <p className="text-sm text-foreground leading-snug">{n.text}</p>
148
+ <p className="text-[10px] text-muted-foreground mt-1 flex items-center gap-1">
149
+ <span className="inline-block w-1 h-1 rounded-full bg-muted-foreground/30" />
150
+ {formatNotificationTime(n.createdAt, t)}
151
+ </p>
152
+ </div>
153
+ <button
154
+ onClick={() => onDismissNotification?.(n.id)}
155
+ className="text-muted-foreground/50 hover:text-foreground shrink-0 transition-colors"
156
+ >
157
+ <X className="h-3 w-3" />
158
+ </button>
159
+ </div>
160
+ ))}
161
+ {notifications.length === 0 && (
162
+ <div className="px-4 py-10 text-center">
163
+ <div className="mx-auto w-10 h-10 rounded-full bg-muted flex items-center justify-center mb-3">
164
+ <Bell className="h-5 w-5 text-muted-foreground/50" />
165
+ </div>
166
+ <p className="text-sm text-muted-foreground">{t('dashboard.notifications.none')}</p>
167
+ </div>
168
+ )}
169
+ </div>
170
+ </div>
171
+ </>
172
+ )}
173
+ </div>
174
+
175
+ <div className="h-6 w-px bg-border mx-1" />
176
+
177
+ <div className="relative">
178
+ <button
179
+ onClick={() => {
180
+ setProfileOpen(!profileOpen);
181
+ setNotifOpen(false);
182
+ }}
183
+ className="flex items-center gap-2 p-1 pl-1 rounded-full hover:bg-muted transition-colors group"
184
+ >
185
+ <div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center text-[10px] font-bold text-primary overflow-hidden border border-primary/20 ring-primary/20 group-hover:ring-4 transition-all">
186
+ {user?.avatar && <img src={user.avatar} alt="User" className="w-full h-full object-cover" />}
187
+ </div>
188
+ <ChevronDown className={`h-4 w-4 text-muted-foreground transition-transform duration-200 ${profileOpen && "rotate-180"}`} />
189
+ </button>
190
+
191
+ {profileOpen && (
192
+ <>
193
+ <div className="fixed inset-0 z-40" onClick={() => setProfileOpen(false)} />
194
+ <div className="absolute top-12 right-0 w-56 bg-popover border border-border rounded-xl shadow-xl z-50 overflow-hidden animate-in fade-in zoom-in-95 duration-200 p-1.5">
195
+ <div className="px-3 py-2 border-b border-border/50 mb-1">
196
+ <p className="text-sm font-bold text-foreground">{user?.name || t("common.roles.user")}</p>
197
+ <p className="text-xs text-muted-foreground truncate">{user?.email}</p>
198
+ </div>
199
+
200
+ <div className="space-y-0.5">
201
+ <button
202
+ onClick={() => { navigate(profileRoute); setProfileOpen(false); }}
203
+ className="flex w-full items-center gap-2.5 px-3 py-2 text-sm text-foreground hover:bg-muted rounded-lg transition-colors"
204
+ >
205
+ <User className="h-4 w-4 text-muted-foreground" />
206
+ {t('common.profile')}
207
+ </button>
208
+ <button
209
+ onClick={() => { navigate(billingRoute); setProfileOpen(false); }}
210
+ className="flex w-full items-center gap-2.5 px-3 py-2 text-sm text-foreground hover:bg-muted rounded-lg transition-colors"
211
+ >
212
+ <CreditCard className="h-4 w-4 text-muted-foreground" />
213
+ {t('common.billing')}
214
+ </button>
215
+ <button
216
+ onClick={() => { navigate(settingsRoute); setProfileOpen(false); }}
217
+ className="flex w-full items-center gap-2.5 px-3 py-2 text-sm text-foreground hover:bg-muted rounded-lg transition-colors"
218
+ >
219
+ <Settings className="h-4 w-4 text-muted-foreground" />
220
+ {t('common.settings')}
221
+ </button>
222
+ </div>
223
+
224
+ <div className="h-px bg-border my-1.5" />
225
+
226
+ <button
227
+ onClick={handleLogout}
228
+ className="flex w-full items-center gap-2.5 px-3 py-2 text-sm text-destructive hover:bg-destructive/10 rounded-lg transition-colors font-medium"
229
+ >
230
+ <LogOut className="h-4 w-4" />
231
+ {t('common.logout')}
232
+ </button>
233
+ </div>
234
+ </>
235
+ )}
236
+ </div>
237
+ </div>
238
+ </header>
239
+ );
240
+ };
@@ -0,0 +1,155 @@
1
+ import { useState, useEffect } from "react";
2
+ import { useLocation, Outlet, Navigate } from "react-router-dom";
3
+ import { Skeleton } from "@umituz/web-design-system/atoms";
4
+ import { DashboardSidebar } from "./DashboardSidebar";
5
+ import { DashboardHeader } from "./DashboardHeader";
6
+ import type { DashboardLayoutConfig } from "../types/layout";
7
+ import type { DashboardNotification } from "../types/notification";
8
+ import type { DashboardUser } from "../types/user";
9
+
10
+ interface DashboardLayoutProps {
11
+ /** Layout configuration */
12
+ config: DashboardLayoutConfig;
13
+ /** Auth user */
14
+ user?: DashboardUser;
15
+ /** Auth loading state */
16
+ authLoading?: boolean;
17
+ /** Authenticated state */
18
+ isAuthenticated?: boolean;
19
+ /** Notifications */
20
+ notifications?: DashboardNotification[];
21
+ /** Logout function */
22
+ onLogout?: () => Promise<void>;
23
+ /** Mark all as read function */
24
+ onMarkAllRead?: () => void;
25
+ /** Dismiss notification function */
26
+ onDismissNotification?: (id: string) => void;
27
+ /** Login route for redirect */
28
+ loginRoute?: string;
29
+ }
30
+
31
+ /**
32
+ * Dashboard Layout Component
33
+ *
34
+ * Main layout wrapper for dashboard pages.
35
+ * Provides sidebar, header, and content area with responsive design.
36
+ *
37
+ * Features:
38
+ * - Collapsible sidebar
39
+ * - Mobile menu overlay
40
+ * - Breadcrumb page titles
41
+ * - Loading skeletons
42
+ * - Auth protection
43
+ *
44
+ * @param props - Dashboard layout props
45
+ */
46
+ export const DashboardLayout = ({
47
+ config,
48
+ user,
49
+ authLoading = false,
50
+ isAuthenticated = true,
51
+ notifications = [],
52
+ onLogout,
53
+ onMarkAllRead,
54
+ onDismissNotification,
55
+ loginRoute = "/login",
56
+ }: DashboardLayoutProps) => {
57
+ const location = useLocation();
58
+ const [collapsed, setCollapsed] = useState(false);
59
+ const [mobileOpen, setMobileOpen] = useState(false);
60
+ const [loading, setLoading] = useState(true);
61
+
62
+ useEffect(() => {
63
+ setLoading(true);
64
+ const timer = setTimeout(() => setLoading(false), 300);
65
+ return () => clearTimeout(timer);
66
+ }, [location.pathname]);
67
+
68
+ useEffect(() => {
69
+ setMobileOpen(false);
70
+ }, [location.pathname]);
71
+
72
+ if (authLoading) return null;
73
+ if (!isAuthenticated) return <Navigate to={loginRoute} replace />;
74
+
75
+ const activeItem = config.sidebarGroups
76
+ .flatMap((group) => group.items)
77
+ .find((i) => i.path === location.pathname);
78
+
79
+ const getTitle = () => {
80
+ if (!activeItem) return config.extraTitleMap?.[location.pathname] || "Dashboard";
81
+ return activeItem.label; // Note: In real app, this would be translated
82
+ };
83
+
84
+ const currentTitle = getTitle();
85
+
86
+ return (
87
+ <div className="flex h-screen w-full bg-background font-sans">
88
+ {/* Desktop Sidebar */}
89
+ <aside
90
+ className={`hidden md:flex flex-col shrink-0 border-r border-sidebar-border bg-sidebar transition-all duration-300 ${
91
+ collapsed ? "w-16" : "w-60"
92
+ }`}
93
+ >
94
+ <DashboardSidebar
95
+ collapsed={collapsed}
96
+ setCollapsed={setCollapsed}
97
+ sidebarGroups={config.sidebarGroups}
98
+ brandName={config.brandName}
99
+ brandTagline={config.brandTagline}
100
+ user={user}
101
+ />
102
+ </aside>
103
+
104
+ {/* Mobile Menu Overlay */}
105
+ {mobileOpen && (
106
+ <div className="fixed inset-0 z-50 md:hidden">
107
+ <div className="absolute inset-0 bg-background/80 backdrop-blur-sm" onClick={() => setMobileOpen(false)} />
108
+ <aside className="absolute left-0 top-0 h-full w-60 border-r border-sidebar-border bg-sidebar shadow-xl">
109
+ <DashboardSidebar
110
+ collapsed={false}
111
+ setCollapsed={() => setMobileOpen(false)}
112
+ sidebarGroups={config.sidebarGroups}
113
+ brandName={config.brandName}
114
+ brandTagline={config.brandTagline}
115
+ user={user}
116
+ />
117
+ </aside>
118
+ </div>
119
+ )}
120
+
121
+ {/* Main Content Area */}
122
+ <div className="flex flex-1 flex-col overflow-hidden min-w-0">
123
+ <DashboardHeader
124
+ collapsed={collapsed}
125
+ setCollapsed={setCollapsed}
126
+ setMobileOpen={setMobileOpen}
127
+ title={currentTitle}
128
+ user={user}
129
+ notifications={notifications}
130
+ onLogout={onLogout}
131
+ onMarkAllRead={onMarkAllRead}
132
+ onDismissNotification={onDismissNotification}
133
+ />
134
+
135
+ <main className="flex-1 overflow-y-auto p-4 md:p-8">
136
+ {loading ? (
137
+ <div className="mx-auto w-full max-w-7xl space-y-6">
138
+ <Skeleton className="h-8 w-1/3 rounded-xl" />
139
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
140
+ {Array.from({ length: 4 }).map((_, i) => (
141
+ <Skeleton key={i} className="h-28 rounded-2xl" />
142
+ ))}
143
+ </div>
144
+ <Skeleton className="h-64 rounded-[32px]" />
145
+ </div>
146
+ ) : (
147
+ <Outlet />
148
+ )}
149
+ </main>
150
+ </div>
151
+ </div>
152
+ );
153
+ };
154
+
155
+ export default DashboardLayout;