@umituz/web-dashboard 2.5.0 → 2.5.2
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/package.json
CHANGED
|
@@ -9,12 +9,33 @@ import type {
|
|
|
9
9
|
Currency,
|
|
10
10
|
PlanTier,
|
|
11
11
|
Subscription,
|
|
12
|
+
SubscriptionStatus,
|
|
12
13
|
PaymentMethod,
|
|
13
14
|
Invoice,
|
|
14
15
|
InvoiceStatus,
|
|
15
16
|
UsageMetric,
|
|
16
17
|
} from "../types/billing";
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Format number with K/M/B suffixes
|
|
21
|
+
*
|
|
22
|
+
* @param num - Number to format
|
|
23
|
+
* @param decimals - Number of decimal places (default: 1)
|
|
24
|
+
* @returns Formatted string
|
|
25
|
+
*/
|
|
26
|
+
export function formatNumber(num: number, decimals: number = 1): string {
|
|
27
|
+
if (num >= 1_000_000_000) {
|
|
28
|
+
return (num / 1_000_000_000).toFixed(decimals) + "B";
|
|
29
|
+
}
|
|
30
|
+
if (num >= 1_000_000) {
|
|
31
|
+
return (num / 1_000_000).toFixed(decimals) + "M";
|
|
32
|
+
}
|
|
33
|
+
if (num >= 1_000) {
|
|
34
|
+
return (num / 1_000).toFixed(decimals) + "K";
|
|
35
|
+
}
|
|
36
|
+
return num.toFixed(decimals);
|
|
37
|
+
}
|
|
38
|
+
|
|
18
39
|
/**
|
|
19
40
|
* Format price with currency
|
|
20
41
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect } from "react";
|
|
2
2
|
import { useLocation, Outlet, Navigate } from "react-router-dom";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
3
4
|
import { Skeleton } from "@umituz/web-design-system/atoms";
|
|
4
5
|
import { DashboardSidebar } from "./DashboardSidebar";
|
|
5
6
|
import { DashboardHeader } from "./DashboardHeader";
|
|
@@ -55,6 +56,7 @@ export const DashboardLayout = ({
|
|
|
55
56
|
loginRoute = "/login",
|
|
56
57
|
}: DashboardLayoutProps) => {
|
|
57
58
|
const location = useLocation();
|
|
59
|
+
const { t } = useTranslation();
|
|
58
60
|
const [collapsed, setCollapsed] = useState(false);
|
|
59
61
|
const [mobileOpen, setMobileOpen] = useState(false);
|
|
60
62
|
const [loading, setLoading] = useState(true);
|
|
@@ -77,8 +79,15 @@ export const DashboardLayout = ({
|
|
|
77
79
|
.find((i) => i.path === location.pathname);
|
|
78
80
|
|
|
79
81
|
const getTitle = () => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
// Check extra title map first (for dynamic routes)
|
|
83
|
+
if (config.extraTitleMap?.[location.pathname]) {
|
|
84
|
+
return config.extraTitleMap[location.pathname];
|
|
85
|
+
}
|
|
86
|
+
// Use translated label from active item
|
|
87
|
+
if (activeItem) {
|
|
88
|
+
return t(activeItem.label);
|
|
89
|
+
}
|
|
90
|
+
return "Dashboard";
|
|
82
91
|
};
|
|
83
92
|
|
|
84
93
|
const currentTitle = getTitle();
|
|
@@ -54,12 +54,12 @@ export const DashboardSidebar = ({
|
|
|
54
54
|
return (
|
|
55
55
|
<div className="flex h-full flex-col">
|
|
56
56
|
{/* Brand Section */}
|
|
57
|
-
<div className="flex h-16 items-center gap-3 border-b border-sidebar-border px-4 transition-all duration-300">
|
|
57
|
+
<div className="flex h-16 items-center gap-3 border-b border-sidebar-border/60 px-4 transition-all duration-300">
|
|
58
58
|
<BrandLogo size={32} />
|
|
59
59
|
{!collapsed && (
|
|
60
|
-
<div className="flex flex-col
|
|
61
|
-
<span className="text-
|
|
62
|
-
<span className="text-[
|
|
60
|
+
<div className="flex flex-col">
|
|
61
|
+
<span className="text-xl font-bold text-sidebar-foreground tracking-tight leading-none">{brandName}</span>
|
|
62
|
+
<span className="text-[10px] font-medium text-primary/80 lowercase tracking-wide mt-0.5 select-none">
|
|
63
63
|
{brandTagline}
|
|
64
64
|
</span>
|
|
65
65
|
</div>
|
|
@@ -67,24 +67,24 @@ export const DashboardSidebar = ({
|
|
|
67
67
|
</div>
|
|
68
68
|
|
|
69
69
|
{/* Create Button */}
|
|
70
|
-
<div className="px-3 py-4 border-b border-sidebar-border/
|
|
70
|
+
<div className="px-3 py-4 border-b border-sidebar-border/60">
|
|
71
71
|
<Link to={createPostRoute}>
|
|
72
72
|
<Button
|
|
73
73
|
variant="default"
|
|
74
|
-
className={`w-full gap-3 shadow-glow transition-all active:scale-95 group overflow-hidden rounded-
|
|
75
|
-
collapsed ? "px-0 justify-center h-10 w-10 mx-auto" : "justify-start px-4 h-
|
|
74
|
+
className={`w-full gap-3 shadow-glow transition-all active:scale-95 group overflow-hidden rounded-lg ${
|
|
75
|
+
collapsed ? "px-0 justify-center h-10 w-10 mx-auto" : "justify-start px-4 h-10"
|
|
76
76
|
}`}
|
|
77
77
|
title={collapsed ? t('sidebar.createPost') : undefined}
|
|
78
78
|
>
|
|
79
79
|
<PenTool className={`shrink-0 transition-transform duration-300 ${collapsed ? "h-5 w-5" : "h-4 w-4 group-hover:scale-110"}`} />
|
|
80
|
-
{!collapsed && <span className="font-
|
|
80
|
+
{!collapsed && <span className="font-semibold tracking-tight">{t('sidebar.createPost')}</span>}
|
|
81
81
|
</Button>
|
|
82
82
|
</Link>
|
|
83
83
|
</div>
|
|
84
84
|
|
|
85
85
|
{/* Navigation */}
|
|
86
|
-
<nav className="flex-1 overflow-y-auto px-2 py-
|
|
87
|
-
<div className="space-y-
|
|
86
|
+
<nav className="flex-1 overflow-y-auto px-2 py-4 scrollbar-hide">
|
|
87
|
+
<div className="space-y-5">
|
|
88
88
|
{sidebarGroups.map((group) => {
|
|
89
89
|
const filteredItems = filterSidebarItems(group.items, user);
|
|
90
90
|
|
|
@@ -97,16 +97,16 @@ export const DashboardSidebar = ({
|
|
|
97
97
|
{!collapsed && (
|
|
98
98
|
<button
|
|
99
99
|
onClick={() => toggleGroup(group.title)}
|
|
100
|
-
className="w-full flex items-center justify-between px-3 py-
|
|
100
|
+
className="w-full flex items-center justify-between px-3 py-1.5 mb-1.5 rounded-md hover:bg-sidebar-accent/15 transition-all duration-200 group/header"
|
|
101
101
|
>
|
|
102
|
-
<span className="text-[10px] font-
|
|
102
|
+
<span className="text-[10px] font-semibold uppercase tracking-wider text-sidebar-foreground/50 group-hover/header:text-sidebar-foreground transition-colors">
|
|
103
103
|
{group.title === "sidebar.ai" ? `${brandName} AI` : t(group.title)}
|
|
104
104
|
</span>
|
|
105
105
|
<div className="flex-shrink-0">
|
|
106
106
|
{isGroupCollapsed ? (
|
|
107
|
-
<ChevronRight className="h-3.5 w-3.5 text-sidebar-foreground/
|
|
107
|
+
<ChevronRight className="h-3.5 w-3.5 text-sidebar-foreground/40 transition-transform duration-200 group-hover/header:text-sidebar-foreground/60" />
|
|
108
108
|
) : (
|
|
109
|
-
<ChevronDown className="h-3.5 w-3.5 text-sidebar-foreground/
|
|
109
|
+
<ChevronDown className="h-3.5 w-3.5 text-sidebar-foreground/40 transition-transform duration-200 group-hover/header:text-sidebar-foreground/60" />
|
|
110
110
|
)}
|
|
111
111
|
</div>
|
|
112
112
|
</button>
|
|
@@ -118,10 +118,10 @@ export const DashboardSidebar = ({
|
|
|
118
118
|
<Link
|
|
119
119
|
key={item.path}
|
|
120
120
|
to={item.path}
|
|
121
|
-
className={`flex items-center gap-3 rounded-
|
|
121
|
+
className={`flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-all duration-200 ${
|
|
122
122
|
active
|
|
123
123
|
? "bg-sidebar-accent text-sidebar-accent-foreground shadow-sm"
|
|
124
|
-
: "text-sidebar-foreground/
|
|
124
|
+
: "text-sidebar-foreground/75 hover:bg-sidebar-accent/12 hover:text-sidebar-foreground"
|
|
125
125
|
} ${collapsed ? "justify-center" : ""}`}
|
|
126
126
|
title={collapsed ? t(item.label) : undefined}
|
|
127
127
|
>
|
|
@@ -137,14 +137,14 @@ export const DashboardSidebar = ({
|
|
|
137
137
|
</nav>
|
|
138
138
|
|
|
139
139
|
{/* Collapse Toggle */}
|
|
140
|
-
<div className="border-t border-sidebar-border p-3">
|
|
140
|
+
<div className="border-t border-sidebar-border/60 p-3">
|
|
141
141
|
<div className={`flex items-center ${collapsed ? "justify-center" : "justify-between"}`}>
|
|
142
142
|
{!collapsed && (
|
|
143
|
-
<p className="text-[10px] uppercase tracking-wider text-sidebar-foreground/
|
|
143
|
+
<p className="text-[10px] uppercase tracking-wider text-sidebar-foreground/50 font-semibold px-2">
|
|
144
144
|
{t('sidebar.system')}
|
|
145
145
|
</p>
|
|
146
146
|
)}
|
|
147
|
-
<Button variant="ghost" size="icon" onClick={() => setCollapsed(!collapsed)} className="text-sidebar-foreground/
|
|
147
|
+
<Button variant="ghost" size="icon" onClick={() => setCollapsed(!collapsed)} className="text-sidebar-foreground/60 hover:text-sidebar-foreground hover:bg-sidebar-accent/15">
|
|
148
148
|
{collapsed ? <Menu className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
|
|
149
149
|
</Button>
|
|
150
150
|
</div>
|