@umituz/web-dashboard 4.7.1 → 5.0.0
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 +62 -88
- package/package.json +5 -7
- package/src/domains/analytics/components/AnalyticsCard.tsx +1 -6
- package/src/domains/analytics/components/AnalyticsChart.tsx +2 -14
- package/src/domains/analytics/components/AnalyticsLayout.tsx +3 -17
- package/src/domains/analytics/components/MetricCard.tsx +1 -6
- package/src/domains/analytics/utils/i18nKeys.ts +0 -9
- package/src/domains/auth/components/AuthLayout.tsx +1 -26
- package/src/domains/auth/components/ForgotPasswordForm.tsx +2 -14
- package/src/domains/auth/components/LoginForm.tsx +2 -15
- package/src/domains/auth/components/RegisterForm.tsx +3 -14
- package/src/domains/auth/components/ResetPasswordForm.tsx +3 -14
- package/src/domains/auth/components/shared/AuthErrorBanner.tsx +1 -11
- package/src/domains/auth/components/shared/EmailInput.tsx +2 -20
- package/src/domains/auth/components/shared/PasswordInput.tsx +3 -22
- package/src/domains/auth/components/shared/PasswordStrengthIndicator.tsx +1 -13
- package/src/domains/auth/components/shared/SocialLoginButtons.tsx +1 -15
- package/src/domains/auth/utils/i18nKeys.ts +0 -18
- package/src/domains/charts/components.tsx +2 -2
- package/src/domains/layouts/components/BrandLogo.tsx +7 -22
- package/src/domains/layouts/components/DashboardHeader.tsx +2 -23
- package/src/domains/layouts/components/DashboardLayout.tsx +2 -39
- package/src/domains/layouts/components/DashboardSidebar.tsx +3 -21
- package/src/domains/permissions/PermissionsContext.tsx +1 -17
- package/src/domains/permissions/utils/i18nKeys.ts +0 -5
- package/src/domains/session/DemoLogin.tsx +2 -10
- package/src/domains/session/OrgSwitcher.tsx +2 -10
- package/src/domains/settings/components/AuditLogList.tsx +1 -10
- package/src/domains/settings/components/PermissionMatrixScreen.tsx +1 -8
- package/src/domains/settings/components/SettingsLayout.tsx +1 -21
- package/src/domains/settings/components/SettingsSection.tsx +1 -13
- package/src/domains/settings/components/UserManagementTable.tsx +1 -8
- package/src/domains/settings/utils/i18nKeys.ts +0 -6
- package/src/index.ts +0 -17
- package/src/styles/dashboard.contract.test.ts +42 -0
- package/src/styles/dashboard.css +483 -920
- package/src/i18n/I18nContext.tsx +0 -33
- package/src/i18n/defaultT.test.ts +0 -43
- package/src/i18n/defaultT.ts +0 -34
- package/src/i18n/index.ts +0 -7
- package/src/i18n/types.ts +0 -13
- package/src/primitives/Button.tsx +0 -24
- package/src/primitives/Checkbox.tsx +0 -17
- package/src/primitives/Input.tsx +0 -23
- package/src/primitives/Skeleton.tsx +0 -6
- package/src/primitives/cn.ts +0 -10
- package/src/primitives/index.ts +0 -23
- package/src/styles/tokens.contract.test.ts +0 -91
- package/src/styles/tokens.css +0 -116
|
@@ -1,36 +1,18 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EmailInput
|
|
3
|
-
*
|
|
4
|
-
* Standardized email input used across all auth forms.
|
|
5
|
-
* Renders label, input, error state, and disabled state consistently.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import { forwardRef } from "react";
|
|
9
|
-
import { Input } from "
|
|
10
|
-
import { cn } from "
|
|
2
|
+
import { Input } from "@umituz/web-design-system/atoms";
|
|
3
|
+
import { cn } from "@umituz/web-design-system/utils/cn";
|
|
11
4
|
|
|
12
5
|
export interface EmailInputProps {
|
|
13
|
-
/** Input id (used to wire up the label) */
|
|
14
6
|
id: string;
|
|
15
|
-
/** Label i18n key — resolved via `translate` */
|
|
16
7
|
labelKey: string;
|
|
17
|
-
/** Placeholder i18n key */
|
|
18
8
|
placeholderKey: string;
|
|
19
|
-
/** Current value */
|
|
20
9
|
value: string;
|
|
21
|
-
/** Change handler */
|
|
22
10
|
onChange: (value: string) => void;
|
|
23
|
-
/** Translation function */
|
|
24
11
|
translate: (key: string) => string;
|
|
25
|
-
/** Auto-complete hint (default: "email") */
|
|
26
12
|
autoComplete?: string;
|
|
27
|
-
/** Disable the input */
|
|
28
13
|
disabled?: boolean;
|
|
29
|
-
/** Whether to mark the field as required (renders an asterisk) */
|
|
30
14
|
required?: boolean;
|
|
31
|
-
/** Optional error message to display below the field */
|
|
32
15
|
error?: string | null;
|
|
33
|
-
/** Custom className */
|
|
34
16
|
className?: string;
|
|
35
17
|
}
|
|
36
18
|
|
|
@@ -1,39 +1,20 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PasswordInput
|
|
3
|
-
*
|
|
4
|
-
* Password field with show/hide toggle, used across all auth forms.
|
|
5
|
-
* Centralizes the visibility toggle, autoComplete, and label structure
|
|
6
|
-
* so individual forms can stay focused on their unique concerns.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
1
|
import { forwardRef, useState } from "react";
|
|
10
2
|
import { Eye, EyeOff } from "lucide-react";
|
|
11
|
-
import { Input } from "
|
|
12
|
-
import { Button } from "
|
|
13
|
-
import { cn } from "
|
|
3
|
+
import { Input } from "@umituz/web-design-system/atoms";
|
|
4
|
+
import { Button } from "@umituz/web-design-system/atoms";
|
|
5
|
+
import { cn } from "@umituz/web-design-system/utils/cn";
|
|
14
6
|
|
|
15
7
|
export interface PasswordInputProps {
|
|
16
|
-
/** Input id (used to wire up the label) */
|
|
17
8
|
id: string;
|
|
18
|
-
/** Label i18n key */
|
|
19
9
|
labelKey: string;
|
|
20
|
-
/** Placeholder i18n key */
|
|
21
10
|
placeholderKey: string;
|
|
22
|
-
/** Current value */
|
|
23
11
|
value: string;
|
|
24
|
-
/** Change handler */
|
|
25
12
|
onChange: (value: string) => void;
|
|
26
|
-
/** Translation function */
|
|
27
13
|
translate: (key: string) => string;
|
|
28
|
-
/** Auto-complete hint (e.g., "current-password", "new-password") */
|
|
29
14
|
autoComplete?: string;
|
|
30
|
-
/** Disable the input */
|
|
31
15
|
disabled?: boolean;
|
|
32
|
-
/** Whether to mark the field as required (renders an asterisk) */
|
|
33
16
|
required?: boolean;
|
|
34
|
-
/** Optional error message to display below the field */
|
|
35
17
|
error?: string | null;
|
|
36
|
-
/** Custom className */
|
|
37
18
|
className?: string;
|
|
38
19
|
}
|
|
39
20
|
|
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PasswordStrengthIndicator
|
|
3
|
-
*
|
|
4
|
-
* Renders a 4-segment strength bar plus a localized label band.
|
|
5
|
-
* The label i18n key is computed from the strength band so the
|
|
6
|
-
* component stays free of hardcoded strings. Bar/label colors come
|
|
7
|
-
* from `--u-*` tones (weak/danger, fair/warning-2, good/warning,
|
|
8
|
-
* strong/success).
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
1
|
import { useMemo } from "react";
|
|
12
2
|
import { calculatePasswordStrength } from "../../utils/auth";
|
|
13
|
-
import { cn } from "
|
|
3
|
+
import { cn } from "@umituz/web-design-system/utils/cn";
|
|
14
4
|
|
|
15
5
|
const STRENGTH_KEYS = {
|
|
16
6
|
weak: 'auth.passwordStrength.weak',
|
|
@@ -29,9 +19,7 @@ const scoreToBand = (score: number): StrengthBand => {
|
|
|
29
19
|
};
|
|
30
20
|
|
|
31
21
|
export interface PasswordStrengthIndicatorProps {
|
|
32
|
-
/** Password to evaluate */
|
|
33
22
|
password: string;
|
|
34
|
-
/** Translation function */
|
|
35
23
|
translate: (key: string) => string;
|
|
36
24
|
}
|
|
37
25
|
|
|
@@ -1,28 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
* SocialLoginButtons
|
|
3
|
-
*
|
|
4
|
-
* Renders a list of social auth providers as a row of icon buttons.
|
|
5
|
-
* Generic over the provider id so adding a new provider is a config change,
|
|
6
|
-
* not a code change.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { Button } from "../../../../primitives";
|
|
1
|
+
import { Button } from "@umituz/web-design-system/atoms";
|
|
10
2
|
import { GoogleIcon, AppleIcon } from "./SocialProviderIcons";
|
|
11
3
|
|
|
12
4
|
export type SocialProviderId = 'google' | 'apple';
|
|
13
5
|
|
|
14
6
|
export interface SocialLoginButtonsProps {
|
|
15
|
-
/** Provider handlers keyed by provider id */
|
|
16
7
|
onProvider: (id: SocialProviderId) => void;
|
|
17
|
-
/** Translation function */
|
|
18
8
|
translate: (key: string) => string;
|
|
19
|
-
/** Disable all buttons (e.g., during form submission) */
|
|
20
9
|
disabled?: boolean;
|
|
21
10
|
}
|
|
22
11
|
|
|
23
|
-
/**
|
|
24
|
-
* Centralized provider config. Add new providers here and the UI follows.
|
|
25
|
-
*/
|
|
26
12
|
const SOCIAL_PROVIDERS: ReadonlyArray<{
|
|
27
13
|
id: SocialProviderId;
|
|
28
14
|
labelKey: string;
|
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth Domain i18n Keys
|
|
3
|
-
*
|
|
4
|
-
* Centralized catalog of i18n keys used across auth components.
|
|
5
|
-
* Components stay free of hardcoded strings and consume these
|
|
6
|
-
* constants — the consumer app provides the actual translations.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
1
|
export const AUTH_KEYS = {
|
|
10
|
-
// Login screen
|
|
11
2
|
login: {
|
|
12
3
|
welcomeBack: 'auth.login.welcomeBack',
|
|
13
4
|
signInTo: 'auth.login.signInTo',
|
|
@@ -20,7 +11,6 @@ export const AUTH_KEYS = {
|
|
|
20
11
|
orContinueWith: 'auth.login.orContinueWith',
|
|
21
12
|
},
|
|
22
13
|
|
|
23
|
-
// Register screen
|
|
24
14
|
register: {
|
|
25
15
|
createAccount: 'auth.register.createAccount',
|
|
26
16
|
join: 'auth.register.join',
|
|
@@ -39,7 +29,6 @@ export const AUTH_KEYS = {
|
|
|
39
29
|
passwordsDoNotMatch: 'auth.register.passwordsDoNotMatch',
|
|
40
30
|
},
|
|
41
31
|
|
|
42
|
-
// Forgot password
|
|
43
32
|
forgotPassword: {
|
|
44
33
|
title: 'auth.forgotPassword.title',
|
|
45
34
|
description: 'auth.forgotPassword.description',
|
|
@@ -55,7 +44,6 @@ export const AUTH_KEYS = {
|
|
|
55
44
|
step3: 'auth.forgotPassword.steps.step3',
|
|
56
45
|
},
|
|
57
46
|
|
|
58
|
-
// Reset password
|
|
59
47
|
resetPassword: {
|
|
60
48
|
title: 'auth.resetPassword.title',
|
|
61
49
|
description: 'auth.resetPassword.description',
|
|
@@ -68,7 +56,6 @@ export const AUTH_KEYS = {
|
|
|
68
56
|
goToSignIn: 'auth.resetPassword.goToSignIn',
|
|
69
57
|
},
|
|
70
58
|
|
|
71
|
-
// Shared fields
|
|
72
59
|
fields: {
|
|
73
60
|
email: 'auth.fields.email',
|
|
74
61
|
password: 'auth.fields.password',
|
|
@@ -81,7 +68,6 @@ export const AUTH_KEYS = {
|
|
|
81
68
|
fullName: 'auth.placeholders.fullName',
|
|
82
69
|
},
|
|
83
70
|
|
|
84
|
-
// Errors
|
|
85
71
|
errors: {
|
|
86
72
|
invalidCredentials: 'auth.errors.invalidCredentials',
|
|
87
73
|
loginFailed: 'auth.errors.loginFailed',
|
|
@@ -96,7 +82,6 @@ export const AUTH_KEYS = {
|
|
|
96
82
|
resetFailed: 'auth.errors.resetFailed',
|
|
97
83
|
},
|
|
98
84
|
|
|
99
|
-
// Password strength
|
|
100
85
|
passwordStrength: {
|
|
101
86
|
weak: 'auth.passwordStrength.weak',
|
|
102
87
|
fair: 'auth.passwordStrength.fair',
|
|
@@ -104,20 +89,17 @@ export const AUTH_KEYS = {
|
|
|
104
89
|
strong: 'auth.passwordStrength.strong',
|
|
105
90
|
},
|
|
106
91
|
|
|
107
|
-
// Social
|
|
108
92
|
social: {
|
|
109
93
|
continueWithGoogle: 'auth.social.continueWithGoogle',
|
|
110
94
|
continueWithApple: 'auth.social.continueWithApple',
|
|
111
95
|
},
|
|
112
96
|
|
|
113
|
-
// Layout shell
|
|
114
97
|
layout: {
|
|
115
98
|
backToHome: 'auth.layout.backToHome',
|
|
116
99
|
orContinueWith: 'auth.layout.orContinueWith',
|
|
117
100
|
allRightsReserved: 'auth.layout.allRightsReserved',
|
|
118
101
|
},
|
|
119
102
|
|
|
120
|
-
// Accessibility
|
|
121
103
|
a11y: {
|
|
122
104
|
showPassword: 'auth.a11y.showPassword',
|
|
123
105
|
hidePassword: 'auth.a11y.hidePassword',
|
|
@@ -244,7 +244,7 @@ export function Donut({
|
|
|
244
244
|
cy={size / 2}
|
|
245
245
|
r={radius}
|
|
246
246
|
fill="none"
|
|
247
|
-
stroke="var(--
|
|
247
|
+
stroke="var(--color-muted)"
|
|
248
248
|
strokeWidth={thickness}
|
|
249
249
|
/>
|
|
250
250
|
{segments.map((seg, i) => {
|
|
@@ -454,7 +454,7 @@ export function LineChart({
|
|
|
454
454
|
cx={xAt(i)}
|
|
455
455
|
cy={yFn(v)}
|
|
456
456
|
r={hover === i ? 3.4 : 0}
|
|
457
|
-
fill="var(--
|
|
457
|
+
fill="var(--color-card)"
|
|
458
458
|
stroke={toneOf(s, si)}
|
|
459
459
|
strokeWidth={2}
|
|
460
460
|
/>
|
|
@@ -1,21 +1,10 @@
|
|
|
1
|
-
import { cn } from "
|
|
1
|
+
import { cn } from "@umituz/web-design-system/utils/cn";
|
|
2
2
|
|
|
3
3
|
interface BrandLogoProps {
|
|
4
4
|
className?: string;
|
|
5
5
|
size?: number;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* BrandLogo Component
|
|
10
|
-
*
|
|
11
|
-
* Displays the application brand logo as an SVG.
|
|
12
|
-
* Colors come from the package tokens (`--u-color-primary`,
|
|
13
|
-
* `--u-color-secondary`) so hosts restyle it by overriding CSS
|
|
14
|
-
* custom properties — no host theme variables required.
|
|
15
|
-
*
|
|
16
|
-
* @param className - Optional CSS classes for styling
|
|
17
|
-
* @param size - Width and height in pixels (default: 32)
|
|
18
|
-
*/
|
|
19
8
|
export const BrandLogo = ({ className, size = 32 }: BrandLogoProps) => {
|
|
20
9
|
return (
|
|
21
10
|
<svg
|
|
@@ -27,24 +16,22 @@ export const BrandLogo = ({ className, size = 32 }: BrandLogoProps) => {
|
|
|
27
16
|
className={cn("udb-brand-logo", className)}
|
|
28
17
|
aria-hidden="true"
|
|
29
18
|
>
|
|
30
|
-
{/* Solid Foundation / Platform */}
|
|
31
19
|
<rect
|
|
32
20
|
x="15"
|
|
33
21
|
y="65"
|
|
34
22
|
width="70"
|
|
35
23
|
height="15"
|
|
36
24
|
rx="4"
|
|
37
|
-
fill="var(--
|
|
25
|
+
fill="var(--color-primary)"
|
|
38
26
|
/>
|
|
39
27
|
|
|
40
|
-
{/* Assembly Paths / Structure */}
|
|
41
28
|
<rect
|
|
42
29
|
x="25"
|
|
43
30
|
y="25"
|
|
44
31
|
width="12"
|
|
45
32
|
height="40"
|
|
46
33
|
rx="2"
|
|
47
|
-
fill="var(--
|
|
34
|
+
fill="var(--color-primary)"
|
|
48
35
|
/>
|
|
49
36
|
<rect
|
|
50
37
|
x="44"
|
|
@@ -52,7 +39,7 @@ export const BrandLogo = ({ className, size = 32 }: BrandLogoProps) => {
|
|
|
52
39
|
width="12"
|
|
53
40
|
height="30"
|
|
54
41
|
rx="2"
|
|
55
|
-
fill="var(--
|
|
42
|
+
fill="var(--color-primary)"
|
|
56
43
|
/>
|
|
57
44
|
<rect
|
|
58
45
|
x="63"
|
|
@@ -60,25 +47,23 @@ export const BrandLogo = ({ className, size = 32 }: BrandLogoProps) => {
|
|
|
60
47
|
width="12"
|
|
61
48
|
height="45"
|
|
62
49
|
rx="2"
|
|
63
|
-
fill="var(--
|
|
50
|
+
fill="var(--color-primary)"
|
|
64
51
|
/>
|
|
65
52
|
|
|
66
|
-
{/* Modern Accent - The 'Assembly' Bridge */}
|
|
67
53
|
<rect
|
|
68
54
|
x="20"
|
|
69
55
|
y="45"
|
|
70
56
|
width="60"
|
|
71
57
|
height="10"
|
|
72
58
|
rx="2"
|
|
73
|
-
fill="var(--
|
|
59
|
+
fill="var(--color-secondary)"
|
|
74
60
|
/>
|
|
75
61
|
|
|
76
|
-
{/* Connection Point / Beacon */}
|
|
77
62
|
<circle
|
|
78
63
|
cx="50"
|
|
79
64
|
cy="20"
|
|
80
65
|
r="5"
|
|
81
|
-
fill="var(--
|
|
66
|
+
fill="var(--color-secondary)"
|
|
82
67
|
/>
|
|
83
68
|
</svg>
|
|
84
69
|
);
|
|
@@ -3,43 +3,25 @@ import {
|
|
|
3
3
|
Bell, X, Sun, Moon, Menu, User, Settings, LogOut,
|
|
4
4
|
ChevronDown
|
|
5
5
|
} from "lucide-react";
|
|
6
|
-
import { Button } from "
|
|
6
|
+
import { Button } from "@umituz/web-design-system/atoms";
|
|
7
7
|
import { useNavigate } from "react-router-dom";
|
|
8
|
-
import { useT } from "
|
|
8
|
+
import { useT } from "@umituz/web-localization/react";
|
|
9
9
|
import type { DashboardHeaderProps, HeaderProfileItem } from "../types/layout";
|
|
10
10
|
import type { DashboardNotification } from "../types/notification";
|
|
11
11
|
import type { DashboardUser } from "../types/user";
|
|
12
12
|
import { formatNotificationTime } from "../utils/dashboard";
|
|
13
13
|
|
|
14
14
|
interface DashboardHeaderPropsExtended extends DashboardHeaderProps {
|
|
15
|
-
/** Auth user */
|
|
16
15
|
user?: DashboardUser;
|
|
17
|
-
/** Notifications */
|
|
18
16
|
notifications?: DashboardNotification[];
|
|
19
|
-
/** Logout function */
|
|
20
17
|
onLogout?: () => Promise<void>;
|
|
21
|
-
/** Mark all as read function */
|
|
22
18
|
onMarkAllRead?: () => void;
|
|
23
|
-
/** Dismiss notification function */
|
|
24
19
|
onDismissNotification?: (id: string) => void;
|
|
25
|
-
/** Settings route */
|
|
26
20
|
settingsRoute?: string;
|
|
27
|
-
/** Profile route */
|
|
28
21
|
profileRoute?: string;
|
|
29
|
-
/** Host-injected profile-menu entries — billing and friends live here,
|
|
30
|
-
* not in the skeleton. */
|
|
31
22
|
extraProfileItems?: HeaderProfileItem[];
|
|
32
23
|
}
|
|
33
24
|
|
|
34
|
-
/**
|
|
35
|
-
* Dashboard Header Component
|
|
36
|
-
*
|
|
37
|
-
* Displays top navigation bar with theme toggle, notifications,
|
|
38
|
-
* user menu, and organisation selector. Styling is entirely
|
|
39
|
-
* `udb-header*` / `udb-menu*` classes from the package stylesheet.
|
|
40
|
-
*
|
|
41
|
-
* @param props - Dashboard header props
|
|
42
|
-
*/
|
|
43
25
|
export const DashboardHeader = ({
|
|
44
26
|
collapsed,
|
|
45
27
|
setCollapsed,
|
|
@@ -72,7 +54,6 @@ export const DashboardHeader = ({
|
|
|
72
54
|
await onLogout?.();
|
|
73
55
|
navigate("/login");
|
|
74
56
|
} catch {
|
|
75
|
-
// Error handling can be added by parent component
|
|
76
57
|
}
|
|
77
58
|
};
|
|
78
59
|
|
|
@@ -93,7 +74,6 @@ export const DashboardHeader = ({
|
|
|
93
74
|
</div>
|
|
94
75
|
|
|
95
76
|
<div className="udb-header__actions">
|
|
96
|
-
{/* Theme Toggle — rendered only when the host app wires it up */}
|
|
97
77
|
{onToggleTheme && (
|
|
98
78
|
<Button
|
|
99
79
|
variant="ghost"
|
|
@@ -107,7 +87,6 @@ export const DashboardHeader = ({
|
|
|
107
87
|
</Button>
|
|
108
88
|
)}
|
|
109
89
|
|
|
110
|
-
{/* Notifications */}
|
|
111
90
|
<div className="udb-header__anchor">
|
|
112
91
|
<Button
|
|
113
92
|
variant="ghost"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect } from "react";
|
|
2
2
|
import { useLocation, Outlet, Navigate } from "react-router-dom";
|
|
3
|
-
import { useT } from "
|
|
4
|
-
import { Skeleton } from "
|
|
3
|
+
import { useT } from "@umituz/web-localization/react";
|
|
4
|
+
import { Skeleton } from "@umituz/web-design-system/atoms";
|
|
5
5
|
import { DashboardSidebar } from "./DashboardSidebar";
|
|
6
6
|
import { DashboardHeader } from "./DashboardHeader";
|
|
7
7
|
import type { DashboardLayoutConfig } from "../types/layout";
|
|
@@ -9,54 +9,20 @@ import type { DashboardNotification } from "../types/notification";
|
|
|
9
9
|
import type { DashboardUser } from "../types/user";
|
|
10
10
|
|
|
11
11
|
interface DashboardLayoutProps {
|
|
12
|
-
/** Layout configuration */
|
|
13
12
|
config: DashboardLayoutConfig;
|
|
14
|
-
/** Auth user */
|
|
15
13
|
user?: DashboardUser;
|
|
16
|
-
/** Auth loading state */
|
|
17
14
|
authLoading?: boolean;
|
|
18
|
-
/** Authenticated state */
|
|
19
15
|
isAuthenticated?: boolean;
|
|
20
|
-
/** Notifications */
|
|
21
16
|
notifications?: DashboardNotification[];
|
|
22
|
-
/** Logout function */
|
|
23
17
|
onLogout?: () => Promise<void>;
|
|
24
|
-
/** Mark all as read function */
|
|
25
18
|
onMarkAllRead?: () => void;
|
|
26
|
-
/** Dismiss notification function */
|
|
27
19
|
onDismissNotification?: (id: string) => void;
|
|
28
|
-
/** Login route for redirect */
|
|
29
20
|
loginRoute?: string;
|
|
30
|
-
/** Current theme, forwarded to the header toggle (default: "light") */
|
|
31
21
|
theme?: "light" | "dark";
|
|
32
|
-
/** Theme toggle handler — header toggle is hidden when omitted */
|
|
33
22
|
onToggleTheme?: () => void;
|
|
34
|
-
/**
|
|
35
|
-
* Artificial route-transition skeleton duration in ms (default: 0 = off).
|
|
36
|
-
* Content renders immediately by default; set > 0 only if you want
|
|
37
|
-
* a skeleton to mask route swaps.
|
|
38
|
-
*/
|
|
39
23
|
routeTransitionMs?: number;
|
|
40
24
|
}
|
|
41
25
|
|
|
42
|
-
/**
|
|
43
|
-
* Dashboard Layout Component
|
|
44
|
-
*
|
|
45
|
-
* Main layout wrapper for dashboard pages.
|
|
46
|
-
* Provides sidebar, header, and content area with responsive design.
|
|
47
|
-
*
|
|
48
|
-
* Features:
|
|
49
|
-
* - Collapsible sidebar
|
|
50
|
-
* - Mobile menu overlay
|
|
51
|
-
* - Breadcrumb page titles
|
|
52
|
-
* - Loading skeletons (opt-in via routeTransitionMs)
|
|
53
|
-
* - Auth protection
|
|
54
|
-
*
|
|
55
|
-
* All styling comes from `udb-*` classes in the package stylesheet
|
|
56
|
-
* (`@umituz/web-dashboard/styles.css`) — no utility framework required.
|
|
57
|
-
*
|
|
58
|
-
* @param props - Dashboard layout props
|
|
59
|
-
*/
|
|
60
26
|
export const DashboardLayout = ({
|
|
61
27
|
config,
|
|
62
28
|
user,
|
|
@@ -109,7 +75,6 @@ export const DashboardLayout = ({
|
|
|
109
75
|
|
|
110
76
|
return (
|
|
111
77
|
<div className="udb-root udb-layout">
|
|
112
|
-
{/* Desktop Sidebar */}
|
|
113
78
|
<aside
|
|
114
79
|
className={`udb-layout__sidebar ${collapsed ? "udb-layout__sidebar--collapsed" : ""}`}
|
|
115
80
|
>
|
|
@@ -123,7 +88,6 @@ export const DashboardLayout = ({
|
|
|
123
88
|
/>
|
|
124
89
|
</aside>
|
|
125
90
|
|
|
126
|
-
{/* Mobile Menu Overlay */}
|
|
127
91
|
{mobileOpen && (
|
|
128
92
|
<div className="udb-layout__mobile">
|
|
129
93
|
<div className="udb-layout__backdrop" onClick={() => setMobileOpen(false)} />
|
|
@@ -140,7 +104,6 @@ export const DashboardLayout = ({
|
|
|
140
104
|
</div>
|
|
141
105
|
)}
|
|
142
106
|
|
|
143
|
-
{/* Main Content Area */}
|
|
144
107
|
<div className="udb-layout__main">
|
|
145
108
|
<DashboardHeader
|
|
146
109
|
collapsed={collapsed}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import { Link, useLocation } from "react-router-dom";
|
|
3
|
-
import { useT } from "
|
|
3
|
+
import { useT } from "@umituz/web-localization/react";
|
|
4
4
|
|
|
5
|
-
import { Button
|
|
5
|
+
import { Button } from "@umituz/web-design-system/atoms";
|
|
6
|
+
import { cn } from "@umituz/web-design-system/utils/cn";
|
|
6
7
|
import { BrandLogo } from "./BrandLogo";
|
|
7
8
|
import { PenTool, Menu, ChevronLeft, ChevronDown, ChevronRight } from "lucide-react";
|
|
8
9
|
import type { DashboardSidebarProps } from "../types/layout";
|
|
@@ -11,29 +12,14 @@ import type { SidebarGroup } from "../types/sidebar";
|
|
|
11
12
|
import { filterSidebarItems } from "../utils/dashboard";
|
|
12
13
|
|
|
13
14
|
interface DashboardSidebarPropsExtended extends DashboardSidebarProps {
|
|
14
|
-
/** Sidebar groups configuration */
|
|
15
15
|
sidebarGroups: SidebarGroup[];
|
|
16
|
-
/** Brand name */
|
|
17
16
|
brandName?: string;
|
|
18
|
-
/** Brand tagline */
|
|
19
17
|
brandTagline?: string;
|
|
20
|
-
/** Create post route */
|
|
21
18
|
createPostRoute?: string;
|
|
22
|
-
/** Whether to show the primary create button (default: true) */
|
|
23
19
|
showCreatePost?: boolean;
|
|
24
|
-
/** Auth user */
|
|
25
20
|
user?: DashboardUser;
|
|
26
21
|
}
|
|
27
22
|
|
|
28
|
-
/**
|
|
29
|
-
* Dashboard Sidebar Component
|
|
30
|
-
*
|
|
31
|
-
* Displays collapsible sidebar with navigation menu items.
|
|
32
|
-
* Supports app-based filtering (mobile/web) and collapsible groups.
|
|
33
|
-
* Styling is entirely `udb-sidebar*` classes from the package stylesheet.
|
|
34
|
-
*
|
|
35
|
-
* @param props - Dashboard sidebar props
|
|
36
|
-
*/
|
|
37
23
|
export const DashboardSidebar = ({
|
|
38
24
|
collapsed,
|
|
39
25
|
setCollapsed,
|
|
@@ -57,7 +43,6 @@ export const DashboardSidebar = ({
|
|
|
57
43
|
|
|
58
44
|
return (
|
|
59
45
|
<div className={cn("udb-sidebar", collapsed && "udb-sidebar--collapsed")}>
|
|
60
|
-
{/* Brand Section */}
|
|
61
46
|
<div className="udb-sidebar__brand">
|
|
62
47
|
<BrandLogo size={32} />
|
|
63
48
|
{!collapsed && (
|
|
@@ -68,7 +53,6 @@ export const DashboardSidebar = ({
|
|
|
68
53
|
)}
|
|
69
54
|
</div>
|
|
70
55
|
|
|
71
|
-
{/* Create Button */}
|
|
72
56
|
{showCreatePost && createPostRoute && (
|
|
73
57
|
<div className="udb-sidebar__create">
|
|
74
58
|
<Link to={createPostRoute}>
|
|
@@ -84,7 +68,6 @@ export const DashboardSidebar = ({
|
|
|
84
68
|
</div>
|
|
85
69
|
)}
|
|
86
70
|
|
|
87
|
-
{/* Navigation */}
|
|
88
71
|
<nav className="udb-sidebar__nav">
|
|
89
72
|
<div className="udb-sidebar__groups">
|
|
90
73
|
{sidebarGroups.map((group) => {
|
|
@@ -135,7 +118,6 @@ export const DashboardSidebar = ({
|
|
|
135
118
|
</div>
|
|
136
119
|
</nav>
|
|
137
120
|
|
|
138
|
-
{/* Collapse Toggle */}
|
|
139
121
|
<div className="udb-sidebar__footer">
|
|
140
122
|
{!collapsed && (
|
|
141
123
|
<p className="udb-sidebar__footer-label">
|
|
@@ -1,20 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Permissions context — provides the matrix + active role and a
|
|
3
|
-
* `can(permission)` helper bound to them. Guard components
|
|
4
|
-
* (<RequirePermission>, <Can>) consume this context.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import { createContext, useCallback, useContext, useMemo, type ReactNode } from "react";
|
|
8
2
|
import { Lock } from "lucide-react";
|
|
9
|
-
import { useT } from "
|
|
3
|
+
import { useT } from "@umituz/web-localization/react";
|
|
10
4
|
import { hasPermission } from "./hasPermission";
|
|
11
5
|
import { PERMISSIONS_KEYS } from "./utils/i18nKeys";
|
|
12
6
|
import type { Permission, PermissionMatrix, RoleKey } from "./types";
|
|
13
7
|
|
|
14
8
|
export interface PermissionsProviderProps {
|
|
15
|
-
/** Role x permission matrix. */
|
|
16
9
|
matrix: PermissionMatrix;
|
|
17
|
-
/** Active user's role. */
|
|
18
10
|
role: RoleKey | null | undefined;
|
|
19
11
|
children: ReactNode;
|
|
20
12
|
}
|
|
@@ -57,7 +49,6 @@ export const usePermissions = (): PermissionsContextValue => {
|
|
|
57
49
|
return context;
|
|
58
50
|
};
|
|
59
51
|
|
|
60
|
-
/** Default lock screen rendered by RequirePermission when denied. */
|
|
61
52
|
const ForbiddenFallback = () => {
|
|
62
53
|
const t = useT();
|
|
63
54
|
return (
|
|
@@ -72,17 +63,11 @@ const ForbiddenFallback = () => {
|
|
|
72
63
|
};
|
|
73
64
|
|
|
74
65
|
export interface RequirePermissionProps {
|
|
75
|
-
/** Permission required to render the children. */
|
|
76
66
|
permission: Permission;
|
|
77
67
|
children: ReactNode;
|
|
78
|
-
/** Rendered instead of the default lock screen when denied. */
|
|
79
68
|
fallback?: ReactNode;
|
|
80
69
|
}
|
|
81
70
|
|
|
82
|
-
/**
|
|
83
|
-
* Page-level permission gate — renders the fallback (lock screen by
|
|
84
|
-
* default) instead of the children when `can(permission)` is false.
|
|
85
|
-
*/
|
|
86
71
|
export const RequirePermission = ({
|
|
87
72
|
permission,
|
|
88
73
|
children,
|
|
@@ -95,7 +80,6 @@ export const RequirePermission = ({
|
|
|
95
80
|
|
|
96
81
|
export interface CanProps extends RequirePermissionProps {}
|
|
97
82
|
|
|
98
|
-
/** Declarative twin of RequirePermission for inline use. */
|
|
99
83
|
export const Can = ({ permission, children, fallback }: CanProps) => (
|
|
100
84
|
<RequirePermission permission={permission} fallback={fallback}>
|
|
101
85
|
{children}
|
|
@@ -1,18 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DemoLogin — one-click demo entry (fleet pattern: log in as a demo
|
|
3
|
-
* persona without a password). Controlled: the click delegates to
|
|
4
|
-
* `onDemo` (typically `session.demoLogin`); the label defaults to
|
|
5
|
-
* the i18n catalog.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import { UserRound } from "lucide-react";
|
|
9
|
-
import { Button } from "
|
|
10
|
-
import { useT } from "
|
|
2
|
+
import { Button } from "@umituz/web-design-system/atoms";
|
|
3
|
+
import { useT } from "@umituz/web-localization/react";
|
|
11
4
|
import { SESSION_KEYS } from "./utils/i18nKeys";
|
|
12
5
|
|
|
13
6
|
export interface DemoLoginProps {
|
|
14
7
|
onDemo: (userId: string) => void | Promise<void>;
|
|
15
|
-
/** Demo user id to log in as. */
|
|
16
8
|
userId: string;
|
|
17
9
|
label?: string;
|
|
18
10
|
variant?: "default" | "outline" | "ghost";
|
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OrgSwitcher — tenant switcher trigger + dropdown.
|
|
3
|
-
*
|
|
4
|
-
* Controlled component: tenants and the active id come in as props,
|
|
5
|
-
* switches are delegated via onSwitch. Styled entirely with existing
|
|
6
|
-
* udb-* classes (profile button, avatar, menu).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
1
|
import { useState } from "react";
|
|
10
2
|
import { Building2, Check, ChevronDown } from "lucide-react";
|
|
11
|
-
import { cn } from "
|
|
12
|
-
import { useT } from "
|
|
3
|
+
import { cn } from "@umituz/web-design-system/utils/cn";
|
|
4
|
+
import { useT } from "@umituz/web-localization/react";
|
|
13
5
|
import { SESSION_KEYS } from "./utils/i18nKeys";
|
|
14
6
|
import type { Tenant } from "./types";
|
|
15
7
|
|