@djangocfg/layouts 2.1.263 → 2.1.264
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.264",
|
|
4
4
|
"description": "Simple, straightforward layout components for Next.js - import and use with props",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"layouts",
|
|
@@ -74,14 +74,14 @@
|
|
|
74
74
|
"check": "tsc --noEmit"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
|
-
"@djangocfg/api": "^2.1.
|
|
78
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
79
|
-
"@djangocfg/i18n": "^2.1.
|
|
80
|
-
"@djangocfg/monitor": "^2.1.
|
|
81
|
-
"@djangocfg/debuger": "^2.1.
|
|
82
|
-
"@djangocfg/ui-core": "^2.1.
|
|
83
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
84
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
77
|
+
"@djangocfg/api": "^2.1.264",
|
|
78
|
+
"@djangocfg/centrifugo": "^2.1.264",
|
|
79
|
+
"@djangocfg/i18n": "^2.1.264",
|
|
80
|
+
"@djangocfg/monitor": "^2.1.264",
|
|
81
|
+
"@djangocfg/debuger": "^2.1.264",
|
|
82
|
+
"@djangocfg/ui-core": "^2.1.264",
|
|
83
|
+
"@djangocfg/ui-nextjs": "^2.1.264",
|
|
84
|
+
"@djangocfg/ui-tools": "^2.1.264",
|
|
85
85
|
"@hookform/resolvers": "^5.2.2",
|
|
86
86
|
"consola": "^3.4.2",
|
|
87
87
|
"lucide-react": "^0.545.0",
|
|
@@ -109,15 +109,15 @@
|
|
|
109
109
|
"uuid": "^11.1.0"
|
|
110
110
|
},
|
|
111
111
|
"devDependencies": {
|
|
112
|
-
"@djangocfg/api": "^2.1.
|
|
113
|
-
"@djangocfg/i18n": "^2.1.
|
|
114
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
115
|
-
"@djangocfg/monitor": "^2.1.
|
|
116
|
-
"@djangocfg/debuger": "^2.1.
|
|
117
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
118
|
-
"@djangocfg/ui-core": "^2.1.
|
|
119
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
120
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
112
|
+
"@djangocfg/api": "^2.1.264",
|
|
113
|
+
"@djangocfg/i18n": "^2.1.264",
|
|
114
|
+
"@djangocfg/centrifugo": "^2.1.264",
|
|
115
|
+
"@djangocfg/monitor": "^2.1.264",
|
|
116
|
+
"@djangocfg/debuger": "^2.1.264",
|
|
117
|
+
"@djangocfg/typescript-config": "^2.1.264",
|
|
118
|
+
"@djangocfg/ui-core": "^2.1.264",
|
|
119
|
+
"@djangocfg/ui-nextjs": "^2.1.264",
|
|
120
|
+
"@djangocfg/ui-tools": "^2.1.264",
|
|
121
121
|
"@types/node": "^24.7.2",
|
|
122
122
|
"@types/react": "^19.1.0",
|
|
123
123
|
"@types/react-dom": "^19.1.0",
|
|
@@ -5,7 +5,7 @@ import moment from 'moment';
|
|
|
5
5
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
6
6
|
|
|
7
7
|
import { useAppT } from '@djangocfg/i18n';
|
|
8
|
-
import { toast } from '@djangocfg/ui-core/hooks';
|
|
8
|
+
import { toast, useImageLoader } from '@djangocfg/ui-core/hooks';
|
|
9
9
|
|
|
10
10
|
import { useAuth } from '@djangocfg/api/auth';
|
|
11
11
|
import {
|
|
@@ -38,6 +38,80 @@ interface ProfileLayoutProps {
|
|
|
38
38
|
enableDeleteAccount?: boolean;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
42
|
+
// Avatar with image check
|
|
43
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
function ProfileAvatar({
|
|
46
|
+
src,
|
|
47
|
+
initials,
|
|
48
|
+
isUploading,
|
|
49
|
+
onChange,
|
|
50
|
+
label,
|
|
51
|
+
}: {
|
|
52
|
+
src?: string | null;
|
|
53
|
+
initials: string;
|
|
54
|
+
isUploading: boolean;
|
|
55
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
56
|
+
label: string;
|
|
57
|
+
}) {
|
|
58
|
+
const { isLoading, isLoaded } = useImageLoader(src ?? undefined);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className="relative group mb-4">
|
|
62
|
+
<Avatar className="w-28 h-28 text-3xl">
|
|
63
|
+
{/* Skeleton while image is loading */}
|
|
64
|
+
{src && isLoading && (
|
|
65
|
+
<div className="w-full h-full rounded-full bg-muted animate-pulse" />
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{/* Image — show only when loaded successfully */}
|
|
69
|
+
{src && (
|
|
70
|
+
<img
|
|
71
|
+
src={src}
|
|
72
|
+
alt=""
|
|
73
|
+
className={cn(
|
|
74
|
+
'w-full h-full object-cover rounded-full transition-opacity duration-300',
|
|
75
|
+
isLoaded ? 'opacity-100' : 'opacity-0 absolute inset-0',
|
|
76
|
+
)}
|
|
77
|
+
/>
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
{/* Fallback — show when no src or image failed */}
|
|
81
|
+
{(!src || (!isLoading && !isLoaded)) && (
|
|
82
|
+
<AvatarFallback className="bg-muted text-muted-foreground text-2xl font-medium">
|
|
83
|
+
{initials}
|
|
84
|
+
</AvatarFallback>
|
|
85
|
+
)}
|
|
86
|
+
</Avatar>
|
|
87
|
+
|
|
88
|
+
{/* Upload overlay */}
|
|
89
|
+
<label
|
|
90
|
+
className={cn(
|
|
91
|
+
'absolute inset-0 rounded-full flex items-center justify-center cursor-pointer',
|
|
92
|
+
'bg-black/0 group-hover:bg-black/40 transition-colors',
|
|
93
|
+
)}
|
|
94
|
+
title={label}
|
|
95
|
+
>
|
|
96
|
+
<div className="opacity-0 group-hover:opacity-100 transition-opacity">
|
|
97
|
+
{isUploading ? (
|
|
98
|
+
<div className="w-6 h-6 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
|
99
|
+
) : (
|
|
100
|
+
<Camera className="w-6 h-6 text-white drop-shadow" />
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
<input
|
|
104
|
+
type="file"
|
|
105
|
+
accept="image/*"
|
|
106
|
+
onChange={onChange}
|
|
107
|
+
className="hidden"
|
|
108
|
+
disabled={isUploading}
|
|
109
|
+
/>
|
|
110
|
+
</label>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
41
115
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
42
116
|
// Profile Content
|
|
43
117
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -150,39 +224,13 @@ const ProfileContent = ({
|
|
|
150
224
|
<div className="container mx-auto px-4 py-12 max-w-md">
|
|
151
225
|
{/* Avatar + header */}
|
|
152
226
|
<div className="flex flex-col items-center mb-12">
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
</AvatarFallback>
|
|
161
|
-
)}
|
|
162
|
-
</Avatar>
|
|
163
|
-
<label
|
|
164
|
-
className={cn(
|
|
165
|
-
'absolute inset-0 rounded-full flex items-center justify-center cursor-pointer',
|
|
166
|
-
'bg-black/0 group-hover:bg-black/40 transition-colors'
|
|
167
|
-
)}
|
|
168
|
-
title={labels.changeAvatar}
|
|
169
|
-
>
|
|
170
|
-
<div className="opacity-0 group-hover:opacity-100 transition-opacity">
|
|
171
|
-
{isUploading ? (
|
|
172
|
-
<div className="w-6 h-6 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
|
173
|
-
) : (
|
|
174
|
-
<Camera className="w-6 h-6 text-white" />
|
|
175
|
-
)}
|
|
176
|
-
</div>
|
|
177
|
-
<input
|
|
178
|
-
type="file"
|
|
179
|
-
accept="image/*"
|
|
180
|
-
onChange={handleAvatarChange}
|
|
181
|
-
className="hidden"
|
|
182
|
-
disabled={isUploading}
|
|
183
|
-
/>
|
|
184
|
-
</label>
|
|
185
|
-
</div>
|
|
227
|
+
<ProfileAvatar
|
|
228
|
+
src={user.avatar}
|
|
229
|
+
initials={getInitials(user.display_username || user.email || '')}
|
|
230
|
+
isUploading={isUploading}
|
|
231
|
+
onChange={handleAvatarChange}
|
|
232
|
+
label={labels.changeAvatar}
|
|
233
|
+
/>
|
|
186
234
|
|
|
187
235
|
<h1 className="text-2xl font-semibold tracking-tight">{displayName}</h1>
|
|
188
236
|
<p className="text-[15px] text-muted-foreground mt-1">{user.email}</p>
|