Skip to content

Commit 05f48c8

Browse files
feat: sidebar (#39)
* style: update (dark) sidebar css variables * feat: sidebar boilerplate and breadcrumb header * some refactor * feat: use collapsible * fix: add tg/grants and remove boilerplate * feat: storage hooks and cookies utility * feat: user nav, category open state persistence * fix: cookie expiration, use <Link> instead of <a> * chore: remove zustand, remove old i18n, biome fixes * feat: sidebar item icons, type cleanup * feat: remove category pages, adjust page padding * chore: biome * style: adjust destructive color variable * style: container mx-auto by default * fix: remove category pages, dont create link for category in breadcrumb * fix: re-render issue with the use-cookie-storage hook * fix: same as previous commit, but for use-session-storage * fix: coderabbit suggestion * fix: match also subroutes in category items * fix: catch JSON parse error * fix: rename column in groups * style: move breadcrumb to center * fix: cleanup home * style: button icon svg size * style: fix padding mismatch between pages and loadings * feat: loading skeleton for account page * perf: make dashboard homepage static for now * feat: add associations management with card component and delete dialog * feat: add No association found message for empty state * Connect getAllAssociations and move client logic from page * Refactor AssociationsList integration * feat: update association card to display initials when logo is not provided * refactor: integrate AssociationsList functionality directly into AssociationsView * feat: implement create, edit, and delete association functionalities * fix: improve getInitials function to remove "-" * feat: error handling for delete, save, edit * feat: add loading state to save and cancel actions * feat: update button variants for success and error states in UI components * feat: implement association links management with dialog and save functionality * fix: review * fix: bring back user details page, link from user list * feat: add back user search, make container flex(col) with pb-6 * fix: missing notFound in user-details, loading style * fix: not found page * fix: add a11y to user details button * feat: update icon for Web section in navigation * fix: validate user ID and update last messages limit * Revert "Merge commit '1df7f77252231489983eb493e1eb7f6f4c5d5652' into sidebar" This reverts commit ecefa99, reversing changes made to fc4fbf8. --------- Co-authored-by: Bianca <ianoselbiancaroberta02@gmail.com>
1 parent 4336f8a commit 05f48c8

54 files changed

Lines changed: 1680 additions & 547 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Skeleton } from "@/components/ui/skeleton"
2+
import { NewPasskeyButton } from "./passkey-button"
3+
4+
export default function Loading() {
5+
return (
6+
<main className="container">
7+
<h2 className="text-accent-foreground mb-4 text-3xl font-bold">Account</h2>
8+
<div className="flex gap-4 mb-12">
9+
<Skeleton className="h-32 w-32 rounded-full" />
10+
11+
<div className="flex flex-col gap-2">
12+
<div className="flex items-center gap-2">
13+
<span className="text-accent-foreground/70">Name:</span>
14+
<Skeleton className="w-40 h-5" />
15+
</div>
16+
17+
<div className="flex items-center gap-2">
18+
<span className="text-accent-foreground/70">Email:</span>
19+
<Skeleton className="w-70 h-5" />
20+
</div>
21+
<div className="flex items-center gap-2">
22+
<span className="text-accent-foreground/70">Telegram:</span>
23+
<Skeleton className="w-60 h-5" />
24+
</div>
25+
</div>
26+
</div>
27+
<div className="flex flex-col gap-4 justify-start items-start">
28+
<h3>Passkeys</h3>
29+
<Skeleton className="w-full h-10" />
30+
<NewPasskeyButton />
31+
</div>
32+
</main>
33+
)
34+
}

src/app/dashboard/(active)/account/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default async function Account() {
2121
const { user } = session
2222

2323
return (
24-
<main className="container mx-auto px-4 py-8">
24+
<main className="container">
2525
<h2 className="text-accent-foreground mb-4 text-3xl font-bold">Account</h2>
2626
<div className="flex gap-4 mb-12">
2727
<ProfilePic user={user} />

src/app/dashboard/(active)/azure/members/loading.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { SkeletonAssocTable } from "./table"
22

33
export default function Loading() {
44
return (
5-
<div className="container p-8">
5+
<div className="container">
66
<SkeletonAssocTable />
77
</div>
88
)

src/app/dashboard/(active)/azure/members/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default async function AssocMembers() {
88
const members = await getAzureMembers()
99

1010
return (
11-
<div className="container p-8">
11+
<div className="container">
1212
<ErrorBoundary fallback={<div>Something went wrong</div>}>
1313
<Suspense fallback={<Spinner />}>
1414
<AssocTable members={members} />

src/app/dashboard/(active)/azure/members/table.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function AssocTable({ members }: { members: AzureMember[] }) {
3333
const users = sociFilter ? members.filter((v) => v.isMember) : members
3434

3535
return (
36-
<div className="space-y-3">
36+
<div className="space-y-3 w-full">
3737
<div className="flex gap-2 items-center">
3838
<p>Utenti MS @polinetwork.org</p>
3939
<Badge
@@ -60,7 +60,7 @@ export function AssocTable({ members }: { members: AzureMember[] }) {
6060

6161
export function SkeletonAssocTable() {
6262
return (
63-
<div className="space-y-3">
63+
<div className="space-y-3 w-full">
6464
<div className="flex gap-2 items-center">
6565
<p>Utenti MS @polinetwork.org</p>
6666
<Badge className="animate-pulse w-15" />

src/app/dashboard/(active)/azure/page.tsx

Lines changed: 0 additions & 29 deletions
This file was deleted.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"use client"
2+
import { usePathname } from "next/navigation"
3+
import { Fragment, useMemo } from "react"
4+
import { NAV_MAP } from "@/components/dashboard-sidebar/data"
5+
import {
6+
BreadcrumbItem as BreadcrumbItemComponent,
7+
BreadcrumbLink,
8+
BreadcrumbList,
9+
BreadcrumbPage,
10+
Breadcrumb as BreadcrumbRoot,
11+
BreadcrumbSeparator,
12+
} from "@/components/ui/breadcrumb"
13+
14+
export type BreadcrumbItem = {
15+
title: string
16+
url?: string
17+
}
18+
19+
export function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
20+
const pathname = usePathname()
21+
const items = useMemo(() => getBreadcrumbs(NAV_MAP, pathname), [pathname])
22+
23+
return (
24+
<BreadcrumbRoot className={className} {...props}>
25+
<BreadcrumbList>
26+
{items.map((item, i) => (
27+
<Fragment key={`breadcrumb-item-${i}`}>
28+
<BreadcrumbItemComponent className="hidden md:block">
29+
{i === items.length - 1 ? (
30+
<BreadcrumbPage>{item.title}</BreadcrumbPage>
31+
) : item.url ? (
32+
<BreadcrumbLink href={item.url}>{item.title}</BreadcrumbLink>
33+
) : (
34+
item.title
35+
)}
36+
</BreadcrumbItemComponent>
37+
{i < items.length - 1 && <BreadcrumbSeparator className="hidden md:block" />}
38+
</Fragment>
39+
))}
40+
</BreadcrumbList>
41+
</BreadcrumbRoot>
42+
)
43+
}
44+
45+
function getBreadcrumbs(navMap: Map<string, string>, pathname: string): BreadcrumbItem[] {
46+
const segments = pathname.split("/").filter(Boolean)
47+
const breadcrumbs: BreadcrumbItem[] = []
48+
49+
let currentPath = ""
50+
let i = 0
51+
52+
for (const segment of segments) {
53+
currentPath += `/${segment}`
54+
let title = navMap.get(currentPath)
55+
if (!title) {
56+
if (isUUIDorId(segment)) {
57+
title = "Details"
58+
} else {
59+
title = segment.charAt(0).toUpperCase() + segment.slice(1)
60+
}
61+
}
62+
63+
// note: at the moment we do not plan to make category pages.
64+
// If such pages are made in the future, this logic can be removed
65+
breadcrumbs.push({ title, url: i !== 1 ? currentPath : undefined })
66+
i++
67+
}
68+
69+
return breadcrumbs
70+
}
71+
72+
function isUUIDorId(segment: string) {
73+
return !Number.isNaN(Number(segment)) || segment.length > 20 // Regex custom a seconda dei tuoi ID
74+
}

src/app/dashboard/(active)/complete-profile.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
"use client"
22

3-
import type { User } from "better-auth"
43
import { UserRoundPenIcon } from "lucide-react"
54
import Link from "next/link"
65
import { Button } from "@/components/ui/button"
6+
import { useSession } from "@/lib/auth"
7+
8+
export function CompleteProfile() {
9+
const { data, isPending } = useSession()
10+
11+
if (!data || isPending) return null
712

8-
export function CompleteProfile({ user }: { user: User }) {
913
return (
10-
!user.name && (
14+
!data.user.name && (
1115
<div className="flex items-center p-2 pl-4 mb-4 gap-2 rounded-lg border text-accent-foreground bg-yellow-400/10 border-yellow-400">
1216
<UserRoundPenIcon size={16} />
1317
<p className="grow">Your profile is incomplete, please enter the missing information.</p>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { cookies } from "next/headers"
2+
import { DashboardSidebar } from "@/components/dashboard-sidebar"
3+
import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
4+
import { COOKIES } from "@/constants"
5+
import { Breadcrumb } from "./breadcrumb"
6+
7+
function parseCookie(cookie: string) {
8+
try {
9+
const parsed = JSON.parse(cookie)
10+
return parsed
11+
} catch (_e) {
12+
return {}
13+
}
14+
}
15+
16+
export default async function AdminLayout({ children }: { children: React.ReactNode }) {
17+
const sidebarCookies = await getSidebarCookies()
18+
19+
return (
20+
<SidebarProvider
21+
defaultOpen={sidebarCookies.open}
22+
style={
23+
{
24+
"--sidebar-width": "19rem",
25+
} as React.CSSProperties
26+
}
27+
>
28+
<DashboardSidebar categoryState={sidebarCookies.state} />
29+
<SidebarInset className="px-4">
30+
<header className="flex h-16 shrink-0 relative items-center justify-between gap-2 border-b mb-8">
31+
<SidebarTrigger className="-ml-1" size="icon" />
32+
<Breadcrumb className="absolute left-1/2 -translate-x-1/2" />
33+
<div></div>
34+
</header>
35+
{children}
36+
</SidebarInset>
37+
</SidebarProvider>
38+
)
39+
}
40+
41+
async function getSidebarCookies() {
42+
const cookieStore = await cookies()
43+
44+
const sidebarCategoryStateCookie = cookieStore.get(COOKIES.SIDEBAR_CATEGORY_STATE)?.value
45+
const sidebarOpenCookie = cookieStore.get(COOKIES.SIDEBAR_OPEN)?.value
46+
const state: Record<string, boolean> = sidebarCategoryStateCookie ? parseCookie(sidebarCategoryStateCookie) : {}
47+
const open: boolean = sidebarOpenCookie !== undefined ? parseCookie(sidebarOpenCookie) : true
48+
49+
return { state, open }
50+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Link from "next/link"
2+
import { Button } from "@/components/ui/button"
3+
4+
export default function NotFound() {
5+
return (
6+
<div className="flex flex-1 flex-col items-center justify-center gap-8 px-4">
7+
<div className="flex flex-col items-center gap-2 text-center">
8+
<h1 className="font-heading text-[12rem] leading-none font-bold tracking-tight text-primary">404</h1>
9+
<h2 className="font-heading text-2xl font-semibold text-foreground">Not found</h2>
10+
<p className="max-w-sm text-muted-foreground">
11+
The resource you&apos;re looking for doesn&apos;t exist or has been moved.
12+
</p>
13+
</div>
14+
<Link href=".">
15+
<Button>Go Back</Button>
16+
</Link>
17+
</div>
18+
)
19+
}

0 commit comments

Comments
 (0)