hazo_auth 0.1.2 → 1.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/hazo_auth_config.example.ini +75 -0
- package/instrumentation.ts +1 -1
- package/next.config.mjs +1 -1
- package/package.json +4 -1
- package/src/app/api/{auth → hazo_auth/auth}/upload_profile_picture/route.ts +2 -2
- package/src/app/api/{auth → hazo_auth}/change_password/route.ts +23 -0
- package/src/app/api/hazo_auth/get_auth/route.ts +89 -0
- package/src/app/api/hazo_auth/invalidate_cache/route.ts +139 -0
- package/src/app/api/{auth → hazo_auth}/library_photos/route.ts +3 -0
- package/src/app/api/{auth → hazo_auth}/logout/route.ts +27 -0
- package/src/app/api/hazo_auth/upload_profile_picture/route.ts +268 -0
- package/src/app/api/hazo_auth/user_management/permissions/route.ts +367 -0
- package/src/app/api/hazo_auth/user_management/roles/route.ts +442 -0
- package/src/app/api/hazo_auth/user_management/users/roles/route.ts +367 -0
- package/src/app/api/hazo_auth/user_management/users/route.ts +239 -0
- package/src/app/api/{auth → hazo_auth}/validate_reset_token/route.ts +3 -0
- package/src/app/api/{auth → hazo_auth}/verify_email/route.ts +3 -0
- package/src/app/globals.css +1 -1
- package/src/app/hazo_auth/user_management/page.tsx +14 -0
- package/src/app/hazo_auth/user_management/user_management_page_client.tsx +16 -0
- package/src/app/hazo_connect/api/sqlite/data/route.ts +7 -1
- package/src/app/hazo_connect/api/sqlite/schema/route.ts +14 -4
- package/src/app/hazo_connect/api/sqlite/tables/route.ts +14 -4
- package/src/app/hazo_connect/sqlite_admin/sqlite-admin-client.tsx +40 -3
- package/src/app/layout.tsx +1 -1
- package/src/app/page.tsx +4 -4
- package/src/components/layouts/email_verification/hooks/use_email_verification.ts +4 -4
- package/src/components/layouts/email_verification/index.tsx +1 -1
- package/src/components/layouts/forgot_password/hooks/use_forgot_password_form.ts +1 -1
- package/src/components/layouts/login/hooks/use_login_form.ts +2 -2
- package/src/components/layouts/my_settings/components/profile_picture_dialog.tsx +1 -1
- package/src/components/layouts/my_settings/components/profile_picture_library_tab.tsx +35 -6
- package/src/components/layouts/my_settings/hooks/use_my_settings.ts +5 -5
- package/src/components/layouts/my_settings/index.tsx +1 -1
- package/src/components/layouts/register/hooks/use_register_form.ts +1 -1
- package/src/components/layouts/reset_password/hooks/use_reset_password_form.ts +3 -3
- package/src/components/layouts/reset_password/index.tsx +2 -2
- package/src/components/layouts/shared/components/logout_button.tsx +1 -1
- package/src/components/layouts/shared/components/profile_pic_menu.tsx +321 -0
- package/src/components/layouts/shared/components/profile_pic_menu_wrapper.tsx +40 -0
- package/src/components/layouts/shared/components/sidebar_layout_wrapper.tsx +22 -72
- package/src/components/layouts/shared/components/unauthorized_guard.tsx +1 -1
- package/src/components/layouts/shared/hooks/use_auth_status.ts +1 -1
- package/src/components/layouts/shared/hooks/use_hazo_auth.ts +158 -0
- package/src/components/layouts/user_management/components/roles_matrix.tsx +607 -0
- package/src/components/layouts/user_management/index.tsx +1295 -0
- package/src/components/ui/alert-dialog.tsx +141 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/dropdown-menu.tsx +201 -0
- package/src/components/ui/table.tsx +120 -0
- package/src/lib/auth/auth_cache.ts +220 -0
- package/src/lib/auth/auth_rate_limiter.ts +121 -0
- package/src/lib/auth/auth_types.ts +65 -0
- package/src/lib/auth/hazo_get_auth.server.ts +333 -0
- package/src/lib/auth_utility_config.server.ts +136 -0
- package/src/lib/hazo_connect_setup.server.ts +2 -3
- package/src/lib/my_settings_config.server.ts +1 -1
- package/src/lib/profile_pic_menu_config.server.ts +138 -0
- package/src/lib/reset_password_config.server.ts +5 -5
- package/src/lib/services/email_service.ts +2 -2
- package/src/lib/services/profile_picture_remove_service.ts +1 -1
- package/src/lib/services/token_service.ts +2 -2
- package/src/lib/user_management_config.server.ts +40 -0
- package/src/lib/utils.ts +1 -1
- package/src/middleware.ts +15 -13
- package/src/server/types/express.d.ts +1 -0
- package/src/stories/project_overview.stories.tsx +1 -1
- package/tailwind.config.ts +1 -1
- /package/src/app/api/{auth → hazo_auth}/forgot_password/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/login/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/me/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/profile_picture/[filename]/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/register/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/remove_profile_picture/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/resend_verification/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/reset_password/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/update_user/route.ts +0 -0
- /package/src/app/{forgot_password → hazo_auth/forgot_password}/forgot_password_page_client.tsx +0 -0
- /package/src/app/{forgot_password → hazo_auth/forgot_password}/page.tsx +0 -0
- /package/src/app/{login → hazo_auth/login}/login_page_client.tsx +0 -0
- /package/src/app/{login → hazo_auth/login}/page.tsx +0 -0
- /package/src/app/{my_settings → hazo_auth/my_settings}/my_settings_page_client.tsx +0 -0
- /package/src/app/{my_settings → hazo_auth/my_settings}/page.tsx +0 -0
- /package/src/app/{register → hazo_auth/register}/page.tsx +0 -0
- /package/src/app/{register → hazo_auth/register}/register_page_client.tsx +0 -0
- /package/src/app/{reset_password → hazo_auth/reset_password}/page.tsx +0 -0
- /package/src/app/{reset_password → hazo_auth/reset_password}/reset_password_page_client.tsx +0 -0
- /package/src/app/{verify_email → hazo_auth/verify_email}/page.tsx +0 -0
- /package/src/app/{verify_email → hazo_auth/verify_email}/verify_email_page_client.tsx +0 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// file_description: profile picture menu component for navbar or sidebar - shows profile picture when logged in, or sign up/sign in buttons when not logged in
|
|
2
|
+
// section: client_directive
|
|
3
|
+
"use client";
|
|
4
|
+
|
|
5
|
+
// section: imports
|
|
6
|
+
import { useState, useMemo } from "react";
|
|
7
|
+
import { useRouter } from "next/navigation";
|
|
8
|
+
import Link from "next/link";
|
|
9
|
+
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
|
10
|
+
import { Button } from "@/components/ui/button";
|
|
11
|
+
import {
|
|
12
|
+
DropdownMenu,
|
|
13
|
+
DropdownMenuContent,
|
|
14
|
+
DropdownMenuItem,
|
|
15
|
+
DropdownMenuSeparator,
|
|
16
|
+
DropdownMenuTrigger,
|
|
17
|
+
} from "@/components/ui/dropdown-menu";
|
|
18
|
+
import { Settings, LogOut } from "lucide-react";
|
|
19
|
+
import { toast } from "sonner";
|
|
20
|
+
import { use_auth_status, trigger_auth_status_refresh } from "@/components/layouts/shared/hooks/use_auth_status";
|
|
21
|
+
// Type-only import from server file is safe (types are erased at runtime)
|
|
22
|
+
import type { ProfilePicMenuMenuItem } from "@/lib/profile_pic_menu_config.server";
|
|
23
|
+
|
|
24
|
+
// section: types
|
|
25
|
+
export type ProfilePicMenuProps = {
|
|
26
|
+
show_single_button?: boolean;
|
|
27
|
+
sign_up_label?: string;
|
|
28
|
+
sign_in_label?: string;
|
|
29
|
+
register_path?: string;
|
|
30
|
+
login_path?: string;
|
|
31
|
+
settings_path?: string;
|
|
32
|
+
logout_path?: string;
|
|
33
|
+
custom_menu_items?: ProfilePicMenuMenuItem[];
|
|
34
|
+
className?: string;
|
|
35
|
+
avatar_size?: "default" | "sm" | "lg";
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// section: component
|
|
39
|
+
/**
|
|
40
|
+
* Profile picture menu component
|
|
41
|
+
* Shows user profile picture when authenticated, or sign up/sign in buttons when not authenticated
|
|
42
|
+
* Clicking profile picture opens dropdown menu with user info and actions
|
|
43
|
+
* @param props - Component props including configuration options
|
|
44
|
+
* @returns Profile picture menu component
|
|
45
|
+
*/
|
|
46
|
+
export function ProfilePicMenu({
|
|
47
|
+
show_single_button = false,
|
|
48
|
+
sign_up_label = "Sign Up",
|
|
49
|
+
sign_in_label = "Sign In",
|
|
50
|
+
register_path = "/hazo_auth/register",
|
|
51
|
+
login_path = "/hazo_auth/login",
|
|
52
|
+
settings_path = "/hazo_auth/my_settings",
|
|
53
|
+
logout_path = "/api/hazo_auth/logout",
|
|
54
|
+
custom_menu_items = [],
|
|
55
|
+
className,
|
|
56
|
+
avatar_size = "default",
|
|
57
|
+
}: ProfilePicMenuProps) {
|
|
58
|
+
const router = useRouter();
|
|
59
|
+
const authStatus = use_auth_status();
|
|
60
|
+
const [isLoggingOut, setIsLoggingOut] = useState(false);
|
|
61
|
+
|
|
62
|
+
// Get initials from name or email
|
|
63
|
+
const getInitials = (): string => {
|
|
64
|
+
if (authStatus.name) {
|
|
65
|
+
const parts = authStatus.name.trim().split(" ");
|
|
66
|
+
if (parts.length >= 2) {
|
|
67
|
+
return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();
|
|
68
|
+
}
|
|
69
|
+
return authStatus.name[0]?.toUpperCase() || "";
|
|
70
|
+
}
|
|
71
|
+
if (authStatus.email) {
|
|
72
|
+
return authStatus.email[0]?.toUpperCase() || "";
|
|
73
|
+
}
|
|
74
|
+
return "?";
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Handle logout
|
|
78
|
+
const handleLogout = async () => {
|
|
79
|
+
setIsLoggingOut(true);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const response = await fetch(logout_path, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
"Content-Type": "application/json",
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const data = await response.json();
|
|
90
|
+
|
|
91
|
+
if (!response.ok || !data.success) {
|
|
92
|
+
throw new Error(data.error || "Logout failed");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
toast.success("Logged out successfully");
|
|
96
|
+
|
|
97
|
+
// Trigger auth status refresh in all components
|
|
98
|
+
trigger_auth_status_refresh();
|
|
99
|
+
|
|
100
|
+
// Refresh the page to update authentication state
|
|
101
|
+
router.refresh();
|
|
102
|
+
} catch (error) {
|
|
103
|
+
const errorMessage =
|
|
104
|
+
error instanceof Error ? error.message : "Logout failed. Please try again.";
|
|
105
|
+
toast.error(errorMessage);
|
|
106
|
+
} finally {
|
|
107
|
+
setIsLoggingOut(false);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Build menu items with default items and custom items
|
|
112
|
+
const menuItems = useMemo(() => {
|
|
113
|
+
const items: ProfilePicMenuMenuItem[] = [];
|
|
114
|
+
|
|
115
|
+
// Add default info items (only if authenticated)
|
|
116
|
+
if (authStatus.authenticated) {
|
|
117
|
+
// User name (info, order: 1)
|
|
118
|
+
if (authStatus.name) {
|
|
119
|
+
items.push({
|
|
120
|
+
type: "info",
|
|
121
|
+
value: authStatus.name,
|
|
122
|
+
order: 1,
|
|
123
|
+
id: "default_name",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Email address (info, order: 2)
|
|
128
|
+
if (authStatus.email) {
|
|
129
|
+
items.push({
|
|
130
|
+
type: "info",
|
|
131
|
+
value: authStatus.email,
|
|
132
|
+
order: 2,
|
|
133
|
+
id: "default_email",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Separator (order: 1)
|
|
138
|
+
items.push({
|
|
139
|
+
type: "separator",
|
|
140
|
+
order: 1,
|
|
141
|
+
id: "default_separator",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Settings (link, order: 1)
|
|
145
|
+
items.push({
|
|
146
|
+
type: "link",
|
|
147
|
+
label: "Settings",
|
|
148
|
+
href: settings_path,
|
|
149
|
+
order: 1,
|
|
150
|
+
id: "default_settings",
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Logout (link, order: 2)
|
|
154
|
+
items.push({
|
|
155
|
+
type: "link",
|
|
156
|
+
label: "Logout",
|
|
157
|
+
href: logout_path,
|
|
158
|
+
order: 2,
|
|
159
|
+
id: "default_logout",
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Add custom menu items
|
|
164
|
+
items.push(...custom_menu_items);
|
|
165
|
+
|
|
166
|
+
// Sort items by type group and order
|
|
167
|
+
// Order: info items first, then separators, then links
|
|
168
|
+
items.sort((a, b) => {
|
|
169
|
+
// Define type priority: info = 0, separator = 1, link = 2
|
|
170
|
+
const typePriority = { info: 0, separator: 1, link: 2 };
|
|
171
|
+
const aPriority = typePriority[a.type];
|
|
172
|
+
const bPriority = typePriority[b.type];
|
|
173
|
+
|
|
174
|
+
if (aPriority !== bPriority) {
|
|
175
|
+
return aPriority - bPriority;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Within same type, sort by order
|
|
179
|
+
return a.order - b.order;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return items;
|
|
183
|
+
}, [authStatus.authenticated, authStatus.name, authStatus.email, settings_path, logout_path, custom_menu_items]);
|
|
184
|
+
|
|
185
|
+
// Avatar size classes
|
|
186
|
+
const avatarSizeClasses = {
|
|
187
|
+
sm: "h-8 w-8",
|
|
188
|
+
default: "h-10 w-10",
|
|
189
|
+
lg: "h-12 w-12",
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Show loading state
|
|
193
|
+
if (authStatus.loading) {
|
|
194
|
+
return (
|
|
195
|
+
<div className={`cls_profile_pic_menu ${className || ""}`}>
|
|
196
|
+
<div className="h-10 w-10 rounded-full bg-slate-200 animate-pulse" />
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Not authenticated - show sign up/sign in buttons
|
|
202
|
+
if (!authStatus.authenticated) {
|
|
203
|
+
return (
|
|
204
|
+
<div className={`cls_profile_pic_menu flex items-center gap-2 ${className || ""}`}>
|
|
205
|
+
{show_single_button ? (
|
|
206
|
+
<Button asChild variant="default" size="sm">
|
|
207
|
+
<Link href={register_path} className="cls_profile_pic_menu_sign_up">
|
|
208
|
+
{sign_up_label}
|
|
209
|
+
</Link>
|
|
210
|
+
</Button>
|
|
211
|
+
) : (
|
|
212
|
+
<>
|
|
213
|
+
<Button asChild variant="outline" size="sm">
|
|
214
|
+
<Link href={register_path} className="cls_profile_pic_menu_sign_up">
|
|
215
|
+
{sign_up_label}
|
|
216
|
+
</Link>
|
|
217
|
+
</Button>
|
|
218
|
+
<Button asChild variant="default" size="sm">
|
|
219
|
+
<Link href={login_path} className="cls_profile_pic_menu_sign_in">
|
|
220
|
+
{sign_in_label}
|
|
221
|
+
</Link>
|
|
222
|
+
</Button>
|
|
223
|
+
</>
|
|
224
|
+
)}
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Authenticated - show profile picture with dropdown menu
|
|
230
|
+
return (
|
|
231
|
+
<div className={`cls_profile_pic_menu ${className || ""}`}>
|
|
232
|
+
<DropdownMenu>
|
|
233
|
+
<DropdownMenuTrigger asChild>
|
|
234
|
+
<button
|
|
235
|
+
className="cls_profile_pic_menu_trigger focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary rounded-full"
|
|
236
|
+
aria-label="Profile menu"
|
|
237
|
+
>
|
|
238
|
+
<Avatar className={`cls_profile_pic_menu_avatar ${avatarSizeClasses[avatar_size]} cursor-pointer`}>
|
|
239
|
+
<AvatarImage
|
|
240
|
+
src={authStatus.profile_picture_url}
|
|
241
|
+
alt={authStatus.name ? `Profile picture of ${authStatus.name}` : "Profile picture"}
|
|
242
|
+
className="cls_profile_pic_menu_image"
|
|
243
|
+
/>
|
|
244
|
+
<AvatarFallback className="cls_profile_pic_menu_fallback bg-slate-200 text-slate-600">
|
|
245
|
+
{getInitials()}
|
|
246
|
+
</AvatarFallback>
|
|
247
|
+
</Avatar>
|
|
248
|
+
</button>
|
|
249
|
+
</DropdownMenuTrigger>
|
|
250
|
+
<DropdownMenuContent align="end" className="cls_profile_pic_menu_dropdown w-56">
|
|
251
|
+
{menuItems.map((item) => {
|
|
252
|
+
if (item.type === "separator") {
|
|
253
|
+
return <DropdownMenuSeparator key={item.id} className="cls_profile_pic_menu_separator" />;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (item.type === "info") {
|
|
257
|
+
return (
|
|
258
|
+
<div key={item.id} className="cls_profile_pic_menu_info">
|
|
259
|
+
{item.value && (
|
|
260
|
+
<div className="cls_profile_pic_menu_info_value px-2 py-1.5 text-sm text-foreground">
|
|
261
|
+
{item.value}
|
|
262
|
+
</div>
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (item.type === "link") {
|
|
269
|
+
// Special handling for logout
|
|
270
|
+
if (item.id === "default_logout") {
|
|
271
|
+
return (
|
|
272
|
+
<DropdownMenuItem
|
|
273
|
+
key={item.id}
|
|
274
|
+
onClick={handleLogout}
|
|
275
|
+
disabled={isLoggingOut}
|
|
276
|
+
className="cls_profile_pic_menu_logout cursor-pointer text-destructive focus:text-destructive"
|
|
277
|
+
>
|
|
278
|
+
<LogOut className="mr-2 h-4 w-4" />
|
|
279
|
+
{isLoggingOut ? "Logging out..." : item.label}
|
|
280
|
+
</DropdownMenuItem>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Special handling for settings
|
|
285
|
+
if (item.id === "default_settings") {
|
|
286
|
+
return (
|
|
287
|
+
<DropdownMenuItem
|
|
288
|
+
key={item.id}
|
|
289
|
+
asChild
|
|
290
|
+
className="cls_profile_pic_menu_settings cursor-pointer"
|
|
291
|
+
>
|
|
292
|
+
<Link href={item.href || settings_path}>
|
|
293
|
+
<Settings className="mr-2 h-4 w-4" />
|
|
294
|
+
{item.label}
|
|
295
|
+
</Link>
|
|
296
|
+
</DropdownMenuItem>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Generic link handling
|
|
301
|
+
return (
|
|
302
|
+
<DropdownMenuItem
|
|
303
|
+
key={item.id}
|
|
304
|
+
asChild
|
|
305
|
+
className="cls_profile_pic_menu_link cursor-pointer"
|
|
306
|
+
>
|
|
307
|
+
<Link href={item.href || "#"}>
|
|
308
|
+
{item.label}
|
|
309
|
+
</Link>
|
|
310
|
+
</DropdownMenuItem>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return null;
|
|
315
|
+
})}
|
|
316
|
+
</DropdownMenuContent>
|
|
317
|
+
</DropdownMenu>
|
|
318
|
+
</div>
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// file_description: server wrapper component that loads profile picture menu configuration and passes to client component
|
|
2
|
+
// section: imports
|
|
3
|
+
import { ProfilePicMenu } from "./profile_pic_menu";
|
|
4
|
+
import { get_profile_pic_menu_config } from "@/lib/profile_pic_menu_config.server";
|
|
5
|
+
|
|
6
|
+
// section: types
|
|
7
|
+
export type ProfilePicMenuWrapperProps = {
|
|
8
|
+
className?: string;
|
|
9
|
+
avatar_size?: "default" | "sm" | "lg";
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// section: component
|
|
13
|
+
/**
|
|
14
|
+
* Server wrapper component that loads profile picture menu configuration from hazo_auth_config.ini
|
|
15
|
+
* and passes it to the client ProfilePicMenu component
|
|
16
|
+
* @param props - Component props including className and avatar_size
|
|
17
|
+
* @returns ProfilePicMenu component with loaded configuration
|
|
18
|
+
*/
|
|
19
|
+
export function ProfilePicMenuWrapper({
|
|
20
|
+
className,
|
|
21
|
+
avatar_size,
|
|
22
|
+
}: ProfilePicMenuWrapperProps) {
|
|
23
|
+
const config = get_profile_pic_menu_config();
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<ProfilePicMenu
|
|
27
|
+
show_single_button={config.show_single_button}
|
|
28
|
+
sign_up_label={config.sign_up_label}
|
|
29
|
+
sign_in_label={config.sign_in_label}
|
|
30
|
+
register_path={config.register_path}
|
|
31
|
+
login_path={config.login_path}
|
|
32
|
+
settings_path={config.settings_path}
|
|
33
|
+
logout_path={config.logout_path}
|
|
34
|
+
custom_menu_items={config.custom_menu_items}
|
|
35
|
+
className={className}
|
|
36
|
+
avatar_size={avatar_size}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
@@ -17,11 +17,9 @@ import {
|
|
|
17
17
|
SidebarTrigger,
|
|
18
18
|
SidebarInset,
|
|
19
19
|
} from "@/components/ui/sidebar";
|
|
20
|
-
import { LogIn, UserPlus, BookOpen, ExternalLink, Database, KeyRound, MailCheck,
|
|
21
|
-
import { use_auth_status
|
|
22
|
-
import {
|
|
23
|
-
import { useRouter } from "next/navigation";
|
|
24
|
-
import { toast } from "sonner";
|
|
20
|
+
import { LogIn, UserPlus, BookOpen, ExternalLink, Database, KeyRound, MailCheck, Key, Settings, User } from "lucide-react";
|
|
21
|
+
import { use_auth_status } from "@/components/layouts/shared/hooks/use_auth_status";
|
|
22
|
+
import { ProfilePicMenu } from "@/components/layouts/shared/components/profile_pic_menu";
|
|
25
23
|
|
|
26
24
|
// section: types
|
|
27
25
|
type SidebarLayoutWrapperProps = {
|
|
@@ -31,36 +29,6 @@ type SidebarLayoutWrapperProps = {
|
|
|
31
29
|
// section: component
|
|
32
30
|
export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
|
|
33
31
|
const authStatus = use_auth_status();
|
|
34
|
-
const router = useRouter();
|
|
35
|
-
|
|
36
|
-
const handleLogout = async () => {
|
|
37
|
-
try {
|
|
38
|
-
const response = await fetch("/api/auth/logout", {
|
|
39
|
-
method: "POST",
|
|
40
|
-
headers: {
|
|
41
|
-
"Content-Type": "application/json",
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const data = await response.json();
|
|
46
|
-
|
|
47
|
-
if (!response.ok || !data.success) {
|
|
48
|
-
throw new Error(data.error || "Logout failed");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
toast.success("Logged out successfully");
|
|
52
|
-
|
|
53
|
-
// Trigger auth status refresh in all components (navbar, sidebar, etc.)
|
|
54
|
-
trigger_auth_status_refresh();
|
|
55
|
-
|
|
56
|
-
// Refresh the page to update authentication state (cookies are cleared server-side)
|
|
57
|
-
router.refresh();
|
|
58
|
-
} catch (error) {
|
|
59
|
-
const errorMessage =
|
|
60
|
-
error instanceof Error ? error.message : "Logout failed. Please try again.";
|
|
61
|
-
toast.error(errorMessage);
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
32
|
|
|
65
33
|
return (
|
|
66
34
|
<SidebarProvider>
|
|
@@ -82,7 +50,7 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
|
|
|
82
50
|
<SidebarMenuItem className="cls_sidebar_layout_test_login_item">
|
|
83
51
|
<SidebarMenuButton asChild>
|
|
84
52
|
<Link
|
|
85
|
-
href="/login"
|
|
53
|
+
href="/hazo_auth/login"
|
|
86
54
|
className="cls_sidebar_layout_test_login_link flex items-center gap-2"
|
|
87
55
|
aria-label="Test login layout component"
|
|
88
56
|
>
|
|
@@ -94,7 +62,7 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
|
|
|
94
62
|
<SidebarMenuItem className="cls_sidebar_layout_test_register_item">
|
|
95
63
|
<SidebarMenuButton asChild>
|
|
96
64
|
<Link
|
|
97
|
-
href="/register"
|
|
65
|
+
href="/hazo_auth/register"
|
|
98
66
|
className="cls_sidebar_layout_test_register_link flex items-center gap-2"
|
|
99
67
|
aria-label="Test register layout component"
|
|
100
68
|
>
|
|
@@ -106,7 +74,7 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
|
|
|
106
74
|
<SidebarMenuItem className="cls_sidebar_layout_test_forgot_password_item">
|
|
107
75
|
<SidebarMenuButton asChild>
|
|
108
76
|
<Link
|
|
109
|
-
href="/forgot_password"
|
|
77
|
+
href="/hazo_auth/forgot_password"
|
|
110
78
|
className="cls_sidebar_layout_test_forgot_password_link flex items-center gap-2"
|
|
111
79
|
aria-label="Test forgot password layout component"
|
|
112
80
|
>
|
|
@@ -118,7 +86,7 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
|
|
|
118
86
|
<SidebarMenuItem className="cls_sidebar_layout_test_reset_password_item">
|
|
119
87
|
<SidebarMenuButton asChild>
|
|
120
88
|
<Link
|
|
121
|
-
href="/reset_password"
|
|
89
|
+
href="/hazo_auth/reset_password"
|
|
122
90
|
className="cls_sidebar_layout_test_reset_password_link flex items-center gap-2"
|
|
123
91
|
aria-label="Test reset password layout component"
|
|
124
92
|
>
|
|
@@ -130,7 +98,7 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
|
|
|
130
98
|
<SidebarMenuItem className="cls_sidebar_layout_test_email_verification_item">
|
|
131
99
|
<SidebarMenuButton asChild>
|
|
132
100
|
<Link
|
|
133
|
-
href="/verify_email"
|
|
101
|
+
href="/hazo_auth/verify_email"
|
|
134
102
|
className="cls_sidebar_layout_test_email_verification_link flex items-center gap-2"
|
|
135
103
|
aria-label="Test email verification layout component"
|
|
136
104
|
>
|
|
@@ -151,6 +119,18 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
|
|
|
151
119
|
</Link>
|
|
152
120
|
</SidebarMenuButton>
|
|
153
121
|
</SidebarMenuItem>
|
|
122
|
+
<SidebarMenuItem className="cls_sidebar_layout_user_management_item">
|
|
123
|
+
<SidebarMenuButton asChild>
|
|
124
|
+
<Link
|
|
125
|
+
href="/hazo_auth/user_management"
|
|
126
|
+
className="cls_sidebar_layout_user_management_link flex items-center gap-2"
|
|
127
|
+
aria-label="Open User Management to manage users, roles, and permissions"
|
|
128
|
+
>
|
|
129
|
+
<User className="h-4 w-4" aria-hidden="true" />
|
|
130
|
+
<span>User Management</span>
|
|
131
|
+
</Link>
|
|
132
|
+
</SidebarMenuButton>
|
|
133
|
+
</SidebarMenuItem>
|
|
154
134
|
</SidebarMenu>
|
|
155
135
|
</SidebarGroup>
|
|
156
136
|
{authStatus.authenticated && (
|
|
@@ -162,7 +142,7 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
|
|
|
162
142
|
<SidebarMenuItem className="cls_sidebar_layout_my_settings_item">
|
|
163
143
|
<SidebarMenuButton asChild>
|
|
164
144
|
<Link
|
|
165
|
-
href="/my_settings"
|
|
145
|
+
href="/hazo_auth/my_settings"
|
|
166
146
|
className="cls_sidebar_layout_my_settings_link flex items-center gap-2"
|
|
167
147
|
aria-label="Open my settings page"
|
|
168
148
|
>
|
|
@@ -171,16 +151,6 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
|
|
|
171
151
|
</Link>
|
|
172
152
|
</SidebarMenuButton>
|
|
173
153
|
</SidebarMenuItem>
|
|
174
|
-
<SidebarMenuItem className="cls_sidebar_layout_logout_item">
|
|
175
|
-
<SidebarMenuButton
|
|
176
|
-
onClick={handleLogout}
|
|
177
|
-
className="cls_sidebar_layout_logout_link flex items-center gap-2"
|
|
178
|
-
aria-label="Logout"
|
|
179
|
-
>
|
|
180
|
-
<LogOut className="h-4 w-4" aria-hidden="true" />
|
|
181
|
-
<span>Logout</span>
|
|
182
|
-
</SidebarMenuButton>
|
|
183
|
-
</SidebarMenuItem>
|
|
184
154
|
</SidebarMenu>
|
|
185
155
|
</SidebarGroup>
|
|
186
156
|
)}
|
|
@@ -231,27 +201,7 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
|
|
|
231
201
|
hazo reusable ui library workspace
|
|
232
202
|
</h2>
|
|
233
203
|
</div>
|
|
234
|
-
<
|
|
235
|
-
{authStatus.loading ? (
|
|
236
|
-
<span className="cls_sidebar_layout_auth_loading text-sm text-muted-foreground">
|
|
237
|
-
Loading...
|
|
238
|
-
</span>
|
|
239
|
-
) : authStatus.authenticated ? (
|
|
240
|
-
<>
|
|
241
|
-
<div className="cls_sidebar_layout_user_info flex items-center gap-2">
|
|
242
|
-
<User className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
|
|
243
|
-
<span className="cls_sidebar_layout_user_name text-sm font-medium text-foreground">
|
|
244
|
-
{authStatus.name || authStatus.email || "Logged in"}
|
|
245
|
-
</span>
|
|
246
|
-
</div>
|
|
247
|
-
<LogoutButton size="sm" />
|
|
248
|
-
</>
|
|
249
|
-
) : (
|
|
250
|
-
<span className="cls_sidebar_layout_not_logged_in text-sm text-muted-foreground">
|
|
251
|
-
Not logged in
|
|
252
|
-
</span>
|
|
253
|
-
)}
|
|
254
|
-
</div>
|
|
204
|
+
<ProfilePicMenu className="cls_sidebar_layout_auth_status" avatar_size="sm" />
|
|
255
205
|
</header>
|
|
256
206
|
<main className="cls_sidebar_layout_main_content flex flex-1 items-center justify-center p-6">
|
|
257
207
|
{children}
|
|
@@ -26,7 +26,7 @@ export type UnauthorizedGuardProps = {
|
|
|
26
26
|
export function UnauthorizedGuard({
|
|
27
27
|
message = "You must be logged in to access this page.",
|
|
28
28
|
loginButtonLabel = "Go to login",
|
|
29
|
-
loginPath = "/login",
|
|
29
|
+
loginPath = "/hazo_auth/login",
|
|
30
30
|
children,
|
|
31
31
|
}: UnauthorizedGuardProps) {
|
|
32
32
|
const router = useRouter();
|
|
@@ -46,7 +46,7 @@ export function use_auth_status(): AuthStatus {
|
|
|
46
46
|
setAuthStatus((prev) => ({ ...prev, loading: true }));
|
|
47
47
|
|
|
48
48
|
try {
|
|
49
|
-
const response = await fetch("/api/
|
|
49
|
+
const response = await fetch("/api/hazo_auth/me", {
|
|
50
50
|
method: "GET",
|
|
51
51
|
credentials: "include",
|
|
52
52
|
});
|