-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Implement project management features #42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
BIA3IA
wants to merge
42
commits into
main
Choose a base branch
from
web/projects
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
3ac11aa
style: update (dark) sidebar css variables
lorenzocorallo aaed723
feat: sidebar boilerplate and breadcrumb header
lorenzocorallo da6760c
some refactor
lorenzocorallo 1d485af
feat: use collapsible
lorenzocorallo ef68226
fix: add tg/grants and remove boilerplate
lorenzocorallo c8ad53a
feat: storage hooks and cookies utility
lorenzocorallo 24828cd
feat: user nav, category open state persistence
lorenzocorallo 1a5d70d
fix: cookie expiration, use <Link> instead of <a>
lorenzocorallo fba8bd3
chore: remove zustand, remove old i18n, biome fixes
lorenzocorallo 73acf8f
feat: sidebar item icons, type cleanup
lorenzocorallo 6099b46
feat: remove category pages, adjust page padding
lorenzocorallo ec3c62e
chore: biome
lorenzocorallo 454e9db
style: adjust destructive color variable
lorenzocorallo fc32ad7
style: container mx-auto by default
lorenzocorallo 51e171e
fix: remove category pages, dont create link for category in breadcrumb
lorenzocorallo 6362566
fix: re-render issue with the use-cookie-storage hook
lorenzocorallo 075f156
fix: same as previous commit, but for use-session-storage
lorenzocorallo 21c64c5
fix: coderabbit suggestion
lorenzocorallo e9fd9e7
fix: match also subroutes in category items
lorenzocorallo 60d3617
fix: catch JSON parse error
lorenzocorallo 2510a36
fix: rename column in groups
lorenzocorallo f6e4fa3
style: move breadcrumb to center
lorenzocorallo fd76b34
fix: cleanup home
lorenzocorallo d7388b5
style: button icon svg size
lorenzocorallo ba20b0b
Merge branch 'main' into sidebar
lorenzocorallo 57d3a23
style: fix padding mismatch between pages and loadings
lorenzocorallo 5003e80
feat: loading skeleton for account page
lorenzocorallo 0b1596c
perf: make dashboard homepage static for now
lorenzocorallo 585f6f6
fix: review
lorenzocorallo 4797e44
fix: bring back user details page, link from user list
lorenzocorallo 61bfddb
feat: add back user search, make container flex(col) with pb-6
lorenzocorallo ce7da3c
fix: missing notFound in user-details, loading style
lorenzocorallo 8f351d4
fix: not found page
lorenzocorallo df0ae86
fix: add a11y to user details button
lorenzocorallo e045aea
feat: implement project management features with create, edit, and de…
BIA3IA 7ebee7a
Merge commit '05f48c88c4525c16f54bcb77bdf6db625076a8f5' into web/proj…
BIA3IA c56888d
fix: update project category references from 'other' to 'general'
BIA3IA 2bbd7d1
refactor: remove unused ButtonGroup component and related code
BIA3IA 0c7cba2
feat: implement sortable functionality for project cards
BIA3IA 432ddd0
feat: add project reordering functionality
BIA3IA c152b14
fix: biome
BIA3IA 44466f3
feat: reorder projects after creation
BIA3IA File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
233 changes: 233 additions & 0 deletions
233
src/app/dashboard/(active)/web/projects/card-project.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,233 @@ | ||
| "use client" | ||
|
|
||
| import { useSortable } from "@dnd-kit/react/sortable" | ||
| 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 { CardProjectProps } from "./types" | ||
|
|
||
| export default function CardProject(item: CardProjectProps) { | ||
| const { ref } = useSortable({ | ||
| id: item.id, | ||
| index: item.sortableIndex ?? 0, | ||
| group: item.category, | ||
| disabled: item.sortableIndex === undefined, | ||
| }) | ||
| 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()) | ||
| } | ||
|
BIA3IA marked this conversation as resolved.
|
||
|
|
||
| // 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 ref={ref} 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" | ||
| /> | ||
|
BIA3IA marked this conversation as resolved.
|
||
| </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> | ||
| ) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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() | ||
|
|
||
| return ( | ||
| <div className="container"> | ||
| <ProjectsView initialProjects={projects} /> | ||
| </div> | ||
| ) | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.