Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
3ac11aa
style: update (dark) sidebar css variables
lorenzocorallo Jun 4, 2026
aaed723
feat: sidebar boilerplate and breadcrumb header
lorenzocorallo Jun 4, 2026
da6760c
some refactor
lorenzocorallo Jun 4, 2026
1d485af
feat: use collapsible
lorenzocorallo Jun 4, 2026
ef68226
fix: add tg/grants and remove boilerplate
lorenzocorallo Jun 5, 2026
c8ad53a
feat: storage hooks and cookies utility
lorenzocorallo Jun 5, 2026
24828cd
feat: user nav, category open state persistence
lorenzocorallo Jun 5, 2026
1a5d70d
fix: cookie expiration, use <Link> instead of <a>
lorenzocorallo Jun 5, 2026
fba8bd3
chore: remove zustand, remove old i18n, biome fixes
lorenzocorallo Jun 5, 2026
73acf8f
feat: sidebar item icons, type cleanup
lorenzocorallo Jun 5, 2026
6099b46
feat: remove category pages, adjust page padding
lorenzocorallo Jun 5, 2026
ec3c62e
chore: biome
lorenzocorallo Jun 5, 2026
454e9db
style: adjust destructive color variable
lorenzocorallo Jun 5, 2026
fc32ad7
style: container mx-auto by default
lorenzocorallo Jun 5, 2026
51e171e
fix: remove category pages, dont create link for category in breadcrumb
lorenzocorallo Jun 5, 2026
6362566
fix: re-render issue with the use-cookie-storage hook
lorenzocorallo Jun 5, 2026
075f156
fix: same as previous commit, but for use-session-storage
lorenzocorallo Jun 5, 2026
21c64c5
fix: coderabbit suggestion
lorenzocorallo Jun 5, 2026
e9fd9e7
fix: match also subroutes in category items
lorenzocorallo Jun 5, 2026
60d3617
fix: catch JSON parse error
lorenzocorallo Jun 5, 2026
2510a36
fix: rename column in groups
lorenzocorallo Jun 5, 2026
f6e4fa3
style: move breadcrumb to center
lorenzocorallo Jun 5, 2026
fd76b34
fix: cleanup home
lorenzocorallo Jun 5, 2026
d7388b5
style: button icon svg size
lorenzocorallo Jun 5, 2026
ba20b0b
Merge branch 'main' into sidebar
lorenzocorallo Jun 6, 2026
57d3a23
style: fix padding mismatch between pages and loadings
lorenzocorallo Jun 6, 2026
5003e80
feat: loading skeleton for account page
lorenzocorallo Jun 6, 2026
0b1596c
perf: make dashboard homepage static for now
lorenzocorallo Jun 6, 2026
585f6f6
fix: review
lorenzocorallo Jun 10, 2026
4797e44
fix: bring back user details page, link from user list
lorenzocorallo Jun 10, 2026
61bfddb
feat: add back user search, make container flex(col) with pb-6
lorenzocorallo Jun 10, 2026
ce7da3c
fix: missing notFound in user-details, loading style
lorenzocorallo Jun 10, 2026
8f351d4
fix: not found page
lorenzocorallo Jun 10, 2026
df0ae86
fix: add a11y to user details button
lorenzocorallo Jun 10, 2026
e045aea
feat: implement project management features with create, edit, and de…
BIA3IA Jun 11, 2026
7ebee7a
Merge commit '05f48c88c4525c16f54bcb77bdf6db625076a8f5' into web/proj…
BIA3IA Jun 11, 2026
c56888d
fix: update project category references from 'other' to 'general'
BIA3IA Jun 11, 2026
2bbd7d1
refactor: remove unused ButtonGroup component and related code
BIA3IA Jun 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 235 additions & 0 deletions src/app/dashboard/(active)/web/projects/card-project.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
"use client"

import { Languages, Link, LucidePencil, Save, Upload, X } from "lucide-react"
import type { ChangeEvent } from "react"
import { useState } from "react"
import { ProjectCategoryMenu } from "@/app/dashboard/(active)/web/projects/category-menu"
import { DeleteDialog } from "@/components/delete-dialog"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardAction, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { getInitials } from "@/lib/utils"
import { cn } from "@/lib/utils/shadcn"
import type { Project, ProjectCategory } from "./types"

export default function CardProject(
item: Project & {
initialEditActive?: boolean
isDraft?: boolean
onCancelCreate?: () => void
onDelete: () => void
onCategoryChange: (category: ProjectCategory) => void
onSave: (values: Project) => boolean | Promise<boolean>
}
) {
const iconInputId = `project-icon-${item.id}`
const [editActive, setEditActive] = useState(item.initialEditActive ?? false)
const [title, setTitle] = useState(item.title)
const [logo, setLogo] = useState(item.logo)
const [descriptionIt, setDescriptionIt] = useState(item.descriptionIt)
const [descriptionEn, setDescriptionEn] = useState(item.descriptionEn)
const [link, setLinks] = useState(item.link)
const [pending, setPending] = useState(false)
const initials = getInitials(title)

async function handleIconUpload(event: ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0]
if (!file) return
setLogo(await file.text())
}

// If it's draft, remove the card, otherwise reset the values to the original ones
function handleCancelEdit() {
if (item.isDraft) {
item.onCancelCreate?.()
return
}

setTitle(item.title)
setLogo(item.logo)
setDescriptionIt(item.descriptionIt)
setDescriptionEn(item.descriptionEn)
setLinks(item.link)
setEditActive(false)
}

// TODO: forse spostare la cosa salvata per ultima nella lista? Perche poi ordinata per id finisce li
// se gli id sono crescenti. O tipo la creo direttamente ultima e non in cima? Pero poi devi scorrere per editarla
async function saveChanges() {
if (pending) return

setPending(true)
try {
const saved = await item.onSave({
id: item.id,
title,
logo,
descriptionIt,
descriptionEn,
link,
category: item.category,
})
if (saved) setEditActive(false)
} finally {
setPending(false)
}
}

function renderIcon() {
if (logo) {
return (
<span
className="flex size-11 items-center justify-center text-foreground [&_svg]:size-full"
aria-hidden="true"
dangerouslySetInnerHTML={{ __html: logo }}
/>
)
}
return (
<span
className="flex size-11 items-center justify-center rounded-lg bg-muted text-sm font-semibold text-muted-foreground"
aria-hidden="true"
>
{initials}
</span>
)
}

return (
<Card key={item.id} className="border border-border bg-card">
<CardHeader className="grid-cols-[auto_1fr_auto] gap-x-4 gap-y-1">
<CardTitle className="flex items-center gap-3 self-center text-lg">
{editActive ? (
<>
<label
htmlFor={iconInputId}
className="group relative flex size-11 shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-lg border border-input bg-background text-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
>
{renderIcon()}
<span className="absolute inset-0 flex items-center justify-center bg-background/80 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100">
<Upload className="size-5" />
</span>
<Input
id={iconInputId}
type="file"
accept=".svg,image/svg+xml"
onChange={handleIconUpload}
aria-label={`Upload ${title} icon`}
className="sr-only"
/>
</label>
<div className="grid w-full gap-2">
<Input
value={title}
onChange={(event) => setTitle(event.target.value)}
className="text-lg font-medium"
/>
</div>
</>
) : (
<>
{renderIcon()}
{title}
</>
)}
</CardTitle>
<CardAction className="flex items-center gap-2">
{editActive ? (
<>
<Button
type="button"
variant="success"
size="icon"
disabled={pending}
onClick={() => saveChanges()}
aria-label={`Save ${title}`}
>
<Save className="size-4" />
</Button>
<Button
type="button"
variant="error"
size="icon"
disabled={pending}
onClick={handleCancelEdit}
aria-label={`Close ${title}`}
>
<X className="size-4" />
</Button>
</>
) : (
<>
<ProjectCategoryMenu category={item.category} onCategoryChange={item.onCategoryChange} />

<Button
type="button"
variant="default"
size="icon"
onClick={() => setEditActive(true)}
aria-label={`Edit ${title}`}
>
<LucidePencil className="size-4" />
</Button>
<DeleteDialog category="Project" name={title} onConfirm={item.onDelete} />
</>
)}
</CardAction>
</CardHeader>

<CardContent className="grid gap-3 md:grid-cols-2">
<div className="flex flex-col gap-2 col-span-2">
<Badge variant="default">
<Link className="size-3" />
Link
</Badge>
<Input
value={link ?? ""}
readOnly={!editActive}
onChange={(event) => setLinks(event.target.value)}
className={cn(
"border-0 bg-transparent p-3 shadow-none",
!editActive &&
"cursor-default resize-none focus-visible:border-transparent focus-visible:ring-0 focus-visible:ring-transparent"
)}
/>
</div>
<div className="flex flex-col gap-2">
<Badge variant="default">
<Languages className="size-3" />
IT
</Badge>
<Textarea
aria-label={`${title} Italian description`}
value={descriptionIt}
readOnly={!editActive}
onChange={(event) => setDescriptionIt(event.target.value)}
className={cn(
"min-h-25 border-0 bg-transparent p-3 shadow-none",
!editActive &&
"cursor-default resize-none focus-visible:border-transparent focus-visible:ring-0 focus-visible:ring-transparent"
)}
/>
</div>
<div className="flex flex-col gap-2">
<Badge variant="default">
<Languages className="size-3" />
EN
</Badge>
<Textarea
aria-label={`${title} English description`}
value={descriptionEn}
readOnly={!editActive}
onChange={(event) => setDescriptionEn(event.target.value)}
className={cn(
"min-h-25 border-0 bg-transparent p-3 shadow-none",
!editActive &&
"cursor-default resize-none focus-visible:border-transparent focus-visible:ring-0 focus-visible:ring-transparent"
)}
/>
</div>
</CardContent>
</Card>
)
}
38 changes: 38 additions & 0 deletions src/app/dashboard/(active)/web/projects/category-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { MoreHorizontalIcon } from "lucide-react"
import { PROJECT_CATEGORIES } from "@/app/dashboard/(active)/web/projects/constants"
import type { ProjectCategory } from "@/app/dashboard/(active)/web/projects/types"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function ProjectCategoryMenu({
category,
onCategoryChange,
}: {
category: ProjectCategory
onCategoryChange: (category: ProjectCategory) => void
}) {
return (
<DropdownMenu>
<DropdownMenuTrigger>
<Button variant="outline" size="icon" aria-label="Move project">
<MoreHorizontalIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuRadioGroup value={category} onValueChange={(value) => onCategoryChange(value as ProjectCategory)}>
{PROJECT_CATEGORIES.map((projectCategory) => (
<DropdownMenuRadioItem key={projectCategory.value} value={projectCategory.value}>
{projectCategory.label}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
)
}
34 changes: 34 additions & 0 deletions src/app/dashboard/(active)/web/projects/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Project } from "./types"

export const PROJECT_CATEGORY = {
NEWS: "news",
GENERAL: "general",
DEPRECATED: "deprecated",
} as const

export const PROJECT_CATEGORIES = [
{
value: PROJECT_CATEGORY.NEWS,
label: "News",
emptyLabel: 'No news projects found. Click "Add Project" to create one.',
},
{
value: PROJECT_CATEGORY.GENERAL,
label: "General",
emptyLabel: 'No general projects found. Click "Add Project" to create one.',
},
{
value: PROJECT_CATEGORY.DEPRECATED,
label: "Deprecated",
emptyLabel: 'No deprecated projects found. Click "Add Project" to create one.',
},
] as const

export const DEFAULT_PROJECT: Omit<Project, "id"> = {
title: "New Project",
logo: null,
descriptionIt: "Description in Italian",
descriptionEn: "Description in English",
link: null,
category: PROJECT_CATEGORY.GENERAL,
}
13 changes: 13 additions & 0 deletions src/app/dashboard/(active)/web/projects/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { trpc } from "@/server/trpc"
import { ProjectsView } from "./projects-view"
import type { Project } from "./types"

export default async function WebProjectsIndex() {
const projects: Project[] = await trpc.web.projects.getAllProjects.query()

Check failure on line 6 in src/app/dashboard/(active)/web/projects/page.tsx

View workflow job for this annotation

GitHub Actions / Typecheck and Lint

Property 'web' does not exist on type 'TRPCClient<BuiltRouter<{ ctx: { userId?: string | undefined; }; meta: object; errorShape: { data: { zodError: { errors: string[]; } | null; code: "UNAUTHORIZED" | "INTERNAL_SERVER_ERROR" | "NOT_FOUND" | "PARSE_ERROR" | ... 15 more ... | "CLIENT_CLOSED_REQUEST"; httpStatus: number; path?: string | undefined; stack?: ...'.

return (
<div className="container">
<ProjectsView initialProjects={projects} />
</div>
)
}
Loading
Loading