@goplusvn/core 0.1.94 → 0.1.95

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/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## 0.1.95 — Triệt tiêu Hydration Mismatch trên AvatarFallback của UserDropdown
2
+
3
+ Trong quá trình Server-Side Rendering (SSR), `useSafeAuthSession()` chưa có phiên đăng nhập
4
+ của client, khiến server render ra AvatarFallback rỗng trong khi client hydrate ngay lập tức
5
+ với initials của user (`"AV"`), dẫn đến lỗi Hydration Mismatch.
6
+
7
+ **Đổi**
8
+
9
+ - `ui/layout/user-dropdown.tsx` — thêm `suppressHydrationWarning` cho `AvatarFallback` ở nút trigger
10
+ và thông tin user trong dropdown menu content; xử lý initials an toàn chống lệch DOM giữa SSR & CSR.
11
+ - `ui/layout/main-layout.tsx`, `vertical-layout.tsx`, `horizontal-layout.tsx`, `vertical-layout-header.tsx`,
12
+ `horizontal-layout-header.tsx` — bổ sung prop `user?: ExtendedUser | null` xuyên suốt, cho phép Server
13
+ Component truyền thông tin session user trực tiếp từ SSR xuống header, triệt tiêu hoàn toàn độ trễ và
14
+ lệch dữ liệu.
15
+
1
16
  ## 0.1.94 — Hỗ trợ Nested Modal Layer cho Combobox/MultiSelect & Chuẩn hoá tìm kiếm Tiếng Việt
2
17
 
3
18
  Khi bộ lọc `Combobox` hoặc `MultiSelect` nằm trong `<Sheet>` (bộ lọc mobile) hoặc `<Dialog>`,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@goplusvn/core",
3
3
  "description": "GoPlusVN Platform Kit - ERP kernel: layout, RBAC, CRUD, multi-tenant, system pages",
4
- "version": "0.1.94",
4
+ "version": "0.1.95",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "registry": "https://registry.npmjs.org",
@@ -25,10 +25,12 @@ export function HorizontalLayoutHeader({
25
25
  dictionary,
26
26
  navigation,
27
27
  notificationSlot,
28
+ user,
28
29
  }: {
29
30
  dictionary: DictionaryType;
30
31
  navigation?: NavigationType[];
31
32
  notificationSlot?: ReactNode;
33
+ user?: any;
32
34
  }) {
33
35
  const params = useParams();
34
36
  const locale = params.lang as LocaleType;
@@ -78,7 +80,7 @@ export function HorizontalLayoutHeader({
78
80
  {/* <ModeDropdown dictionary={dictionary} /> */}
79
81
  {/* <LanguageDropdown dictionary={dictionary} /> */}
80
82
  {/* User dropdown */}
81
- <UserDropdown dictionary={dictionary} locale={locale} />
83
+ <UserDropdown dictionary={dictionary} locale={locale} user={user} />
82
84
  </div>
83
85
  </div>
84
86
  <PageTabs dictionary={dictionary} />
@@ -17,6 +17,7 @@ interface HorizontalLayoutProps {
17
17
  searchResults?: any[];
18
18
  searchLoading?: boolean;
19
19
  notificationSlot?: ReactNode;
20
+ user?: any;
20
21
  }
21
22
 
22
23
  export function HorizontalLayout({
@@ -27,6 +28,7 @@ export function HorizontalLayout({
27
28
  searchResults,
28
29
  searchLoading,
29
30
  notificationSlot,
31
+ user,
30
32
  }: HorizontalLayoutProps) {
31
33
  return (
32
34
  <SidebarProvider>
@@ -42,6 +44,7 @@ export function HorizontalLayout({
42
44
  dictionary={dictionary}
43
45
  navigation={navigation}
44
46
  notificationSlot={notificationSlot}
47
+ user={user}
45
48
  />
46
49
  <main className="w-full mx-auto flex-1 min-h-0 px-4 md:px-8 py-4">
47
50
  {children}
@@ -21,6 +21,11 @@ interface LayoutProps {
21
21
  * dropdown tĩnh cũ (backward-compat cho consumer chưa nối dữ liệu).
22
22
  */
23
23
  notificationSlot?: ReactNode;
24
+ /**
25
+ * Thông tin user đăng nhập (truyền từ Server Component session để triệt tiêu
26
+ * hoàn toàn độ trễ / hydration mismatch trên AvatarFallback ở header).
27
+ */
28
+ user?: any;
24
29
  }
25
30
 
26
31
  export function MainLayout({
@@ -31,6 +36,7 @@ export function MainLayout({
31
36
  searchResults,
32
37
  searchLoading,
33
38
  notificationSlot,
39
+ user,
34
40
  }: LayoutProps) {
35
41
  const mounted = useMounted();
36
42
  const isVertical = useIsVertical();
@@ -48,6 +54,7 @@ export function MainLayout({
48
54
  searchResults={searchResults}
49
55
  searchLoading={searchLoading}
50
56
  notificationSlot={notificationSlot}
57
+ user={user}
51
58
  >
52
59
  {children}
53
60
  </VerticalLayout>
@@ -59,6 +66,7 @@ export function MainLayout({
59
66
  searchResults={searchResults}
60
67
  searchLoading={searchLoading}
61
68
  notificationSlot={notificationSlot}
69
+ user={user}
62
70
  >
63
71
  {children}
64
72
  </HorizontalLayout>
@@ -93,8 +93,11 @@ export function UserDropdown({
93
93
  alt={user?.name || ""}
94
94
  className="object-cover"
95
95
  />
96
- <AvatarFallback className="bg-primary text-white text-[10px] font-bold uppercase">
97
- {user?.name && getInitials(user.name)}
96
+ <AvatarFallback
97
+ className="bg-primary text-white text-[10px] font-bold uppercase"
98
+ suppressHydrationWarning
99
+ >
100
+ {user?.name ? getInitials(user.name) : null}
98
101
  </AvatarFallback>
99
102
  </Avatar>
100
103
  </Button>
@@ -116,15 +119,21 @@ export function UserDropdown({
116
119
  <DropdownMenuLabel className="flex gap-2 p-2">
117
120
  <Avatar className="size-8 border border-border/20 shadow-sm">
118
121
  <AvatarImage src={user?.avatar || undefined} alt="Avatar" />
119
- <AvatarFallback className="bg-transparent">
120
- {user?.name && getInitials(user.name)}
122
+ <AvatarFallback className="bg-transparent" suppressHydrationWarning>
123
+ {user?.name ? getInitials(user.name) : null}
121
124
  </AvatarFallback>
122
125
  </Avatar>
123
126
  <div className="flex flex-col min-w-0 space-y-0.5 overflow-hidden">
124
- <p className="text-sm font-semibold text-foreground truncate leading-none">
127
+ <p
128
+ className="text-sm font-semibold text-foreground truncate leading-none"
129
+ suppressHydrationWarning
130
+ >
125
131
  {user?.name}
126
132
  </p>
127
- <p className="text-[11px] text-muted-foreground truncate font-medium opacity-80 leading-none">
133
+ <p
134
+ className="text-[11px] text-muted-foreground truncate font-medium opacity-80 leading-none"
135
+ suppressHydrationWarning
136
+ >
128
137
  {user?.email}
129
138
  </p>
130
139
  </div>
@@ -19,9 +19,11 @@ import { ToggleMobileSidebar } from "./toggle-mobile-sidebar";
19
19
  export function VerticalLayoutHeader({
20
20
  dictionary,
21
21
  notificationSlot,
22
+ user,
22
23
  }: {
23
24
  dictionary: DictionaryType;
24
25
  notificationSlot?: ReactNode;
26
+ user?: any;
25
27
  }) {
26
28
  const params = useParams();
27
29
  const locale = params.lang as LocaleType;
@@ -67,7 +69,7 @@ export function VerticalLayoutHeader({
67
69
  {/* <ModeDropdown dictionary={dictionary} /> */}
68
70
  {/* <LanguageDropdown dictionary={dictionary} /> */}
69
71
  {/* User dropdown */}
70
- <UserDropdown dictionary={dictionary} locale={locale} />
72
+ <UserDropdown dictionary={dictionary} locale={locale} user={user} />
71
73
  </div>
72
74
  </div>
73
75
  </header>
@@ -17,6 +17,7 @@ interface VerticalLayoutProps {
17
17
  searchResults?: any[];
18
18
  searchLoading?: boolean;
19
19
  notificationSlot?: ReactNode;
20
+ user?: any;
20
21
  }
21
22
 
22
23
  export function VerticalLayout({
@@ -27,6 +28,7 @@ export function VerticalLayout({
27
28
  searchResults,
28
29
  searchLoading,
29
30
  notificationSlot,
31
+ user,
30
32
  }: VerticalLayoutProps) {
31
33
  return (
32
34
  <SidebarProvider>
@@ -41,6 +43,7 @@ export function VerticalLayout({
41
43
  <VerticalLayoutHeader
42
44
  dictionary={dictionary}
43
45
  notificationSlot={notificationSlot}
46
+ user={user}
44
47
  />
45
48
  {/* `h-dvh` ở SidebarInset (không phải `h-screen`): trên Safari/Chrome
46
49
  mobile 100vh tính CẢ thanh URL co giãn, nên đáy trang — phân trang,
@@ -22,7 +22,7 @@
22
22
  "prepare": "husky || true"
23
23
  },
24
24
  "dependencies": {
25
- "@goerp/core": "npm:@goplusvn/core@^0.1.94",
25
+ "@goerp/core": "npm:@goplusvn/core@^0.1.95",
26
26
  "@hookform/resolvers": "3.9.0",
27
27
  "@prisma/adapter-pg": "^7.0.0",
28
28
  "@prisma/client": "^7.0.0",