Mirror of
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #14 from trueberryless-org/delete-options

make projects, task and session deleteable; deploy

authored by

trueberryless and committed by
GitHub
5bf6b6c0 dab8305f

+520 -24
+1
.gitignore
··· 34 34 # typescript 35 35 *.tsbuildinfo 36 36 next-env.d.ts 37 + package-lock.json
+1
package.json
··· 13 13 }, 14 14 "dependencies": { 15 15 "@hookform/resolvers": "^3.7.0", 16 + "@radix-ui/react-alert-dialog": "^1.1.1", 16 17 "@radix-ui/react-avatar": "^1.1.0", 17 18 "@radix-ui/react-checkbox": "^1.1.1", 18 19 "@radix-ui/react-dialog": "^1.1.1",
+33 -3
pnpm-lock.yaml
··· 11 11 '@hookform/resolvers': 12 12 specifier: ^3.7.0 13 13 version: 3.7.0(react-hook-form@7.52.1(react@18.3.1)) 14 + '@radix-ui/react-alert-dialog': 15 + specifier: ^1.1.1 16 + version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 14 17 '@radix-ui/react-avatar': 15 18 specifier: ^1.1.0 16 19 version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) ··· 379 382 380 383 '@radix-ui/primitive@1.1.0': 381 384 resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} 385 + 386 + '@radix-ui/react-alert-dialog@1.1.1': 387 + resolution: {integrity: sha512-wmCoJwj7byuVuiLKqDLlX7ClSUU0vd9sdCeM+2Ls+uf13+cpSJoMgwysHq1SGVVkJj5Xn0XWi1NoRCdkMpr6Mw==} 388 + peerDependencies: 389 + '@types/react': '*' 390 + '@types/react-dom': '*' 391 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 392 + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 393 + peerDependenciesMeta: 394 + '@types/react': 395 + optional: true 396 + '@types/react-dom': 397 + optional: true 382 398 383 399 '@radix-ui/react-arrow@1.1.0': 384 400 resolution: {integrity: sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==} ··· 2938 2954 2939 2955 '@radix-ui/primitive@1.1.0': {} 2940 2956 2957 + '@radix-ui/react-alert-dialog@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 2958 + dependencies: 2959 + '@radix-ui/primitive': 1.1.0 2960 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) 2961 + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1) 2962 + '@radix-ui/react-dialog': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 2963 + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 2964 + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1) 2965 + react: 18.3.1 2966 + react-dom: 18.3.1(react@18.3.1) 2967 + optionalDependencies: 2968 + '@types/react': 18.3.3 2969 + '@types/react-dom': 18.3.0 2970 + 2941 2971 '@radix-ui/react-arrow@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 2942 2972 dependencies: 2943 2973 '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) ··· 4195 4225 eslint: 8.57.0 4196 4226 eslint-import-resolver-node: 0.3.9 4197 4227 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) 4198 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) 4228 + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) 4199 4229 eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) 4200 4230 eslint-plugin-react: 7.34.3(eslint@8.57.0) 4201 4231 eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) ··· 4219 4249 enhanced-resolve: 5.17.0 4220 4250 eslint: 8.57.0 4221 4251 eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) 4222 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) 4252 + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) 4223 4253 fast-glob: 3.3.2 4224 4254 get-tsconfig: 4.7.5 4225 4255 is-core-module: 2.14.0 ··· 4241 4271 transitivePeerDependencies: 4242 4272 - supports-color 4243 4273 4244 - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): 4274 + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): 4245 4275 dependencies: 4246 4276 array-includes: 3.1.8 4247 4277 array.prototype.findlastindex: 1.2.5
+7 -1
public/data/test_user.json
··· 379 379 "exportReminder": "weekly", 380 380 "theme": "default", 381 381 "automation": { 382 - "projectStatusKickoff": true 382 + "archiveStopSessions": true, 383 + "archiveProjectStatusRetirement": true, 384 + "archiveTaskStatusRetirement": true, 385 + "projectStatusKickoff": true, 386 + "projectStatusRetirement": true, 387 + "taskStatusKickoff": true, 388 + "taskStatusRetirement": true 383 389 } 384 390 }, 385 391 "visits": [],
+6
src/components/sign-up.tsx
··· 71 71 exportReminder: "weekly", 72 72 theme: "default", 73 73 automation: { 74 + archiveStopSessions: true, 75 + archiveProjectStatusRetirement: true, 76 + archiveTaskStatusRetirement: true, 74 77 projectStatusKickoff: true, 78 + projectStatusRetirement: true, 79 + taskStatusKickoff: true, 80 + taskStatusRetirement: true 75 81 }, 76 82 }, 77 83 visits: [],
+141
src/components/ui/alert-dialog.tsx
··· 1 + "use client" 2 + 3 + import * as React from "react" 4 + import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" 5 + 6 + import { cn } from "@/lib/utils" 7 + import { buttonVariants } from "@/components/ui/button" 8 + 9 + const AlertDialog = AlertDialogPrimitive.Root 10 + 11 + const AlertDialogTrigger = AlertDialogPrimitive.Trigger 12 + 13 + const AlertDialogPortal = AlertDialogPrimitive.Portal 14 + 15 + const AlertDialogOverlay = React.forwardRef< 16 + React.ElementRef<typeof AlertDialogPrimitive.Overlay>, 17 + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay> 18 + >(({ className, ...props }, ref) => ( 19 + <AlertDialogPrimitive.Overlay 20 + className={cn( 21 + "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", 22 + className 23 + )} 24 + {...props} 25 + ref={ref} 26 + /> 27 + )) 28 + AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName 29 + 30 + const AlertDialogContent = React.forwardRef< 31 + React.ElementRef<typeof AlertDialogPrimitive.Content>, 32 + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content> 33 + >(({ className, ...props }, ref) => ( 34 + <AlertDialogPortal> 35 + <AlertDialogOverlay /> 36 + <AlertDialogPrimitive.Content 37 + ref={ref} 38 + className={cn( 39 + "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", 40 + className 41 + )} 42 + {...props} 43 + /> 44 + </AlertDialogPortal> 45 + )) 46 + AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName 47 + 48 + const AlertDialogHeader = ({ 49 + className, 50 + ...props 51 + }: React.HTMLAttributes<HTMLDivElement>) => ( 52 + <div 53 + className={cn( 54 + "flex flex-col space-y-2 text-center sm:text-left", 55 + className 56 + )} 57 + {...props} 58 + /> 59 + ) 60 + AlertDialogHeader.displayName = "AlertDialogHeader" 61 + 62 + const AlertDialogFooter = ({ 63 + className, 64 + ...props 65 + }: React.HTMLAttributes<HTMLDivElement>) => ( 66 + <div 67 + className={cn( 68 + "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", 69 + className 70 + )} 71 + {...props} 72 + /> 73 + ) 74 + AlertDialogFooter.displayName = "AlertDialogFooter" 75 + 76 + const AlertDialogTitle = React.forwardRef< 77 + React.ElementRef<typeof AlertDialogPrimitive.Title>, 78 + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title> 79 + >(({ className, ...props }, ref) => ( 80 + <AlertDialogPrimitive.Title 81 + ref={ref} 82 + className={cn("text-lg font-semibold", className)} 83 + {...props} 84 + /> 85 + )) 86 + AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName 87 + 88 + const AlertDialogDescription = React.forwardRef< 89 + React.ElementRef<typeof AlertDialogPrimitive.Description>, 90 + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description> 91 + >(({ className, ...props }, ref) => ( 92 + <AlertDialogPrimitive.Description 93 + ref={ref} 94 + className={cn("text-sm text-muted-foreground", className)} 95 + {...props} 96 + /> 97 + )) 98 + AlertDialogDescription.displayName = 99 + AlertDialogPrimitive.Description.displayName 100 + 101 + const AlertDialogAction = React.forwardRef< 102 + React.ElementRef<typeof AlertDialogPrimitive.Action>, 103 + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action> 104 + >(({ className, ...props }, ref) => ( 105 + <AlertDialogPrimitive.Action 106 + ref={ref} 107 + className={cn(buttonVariants(), className)} 108 + {...props} 109 + /> 110 + )) 111 + AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName 112 + 113 + const AlertDialogCancel = React.forwardRef< 114 + React.ElementRef<typeof AlertDialogPrimitive.Cancel>, 115 + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel> 116 + >(({ className, ...props }, ref) => ( 117 + <AlertDialogPrimitive.Cancel 118 + ref={ref} 119 + className={cn( 120 + buttonVariants({ variant: "outline" }), 121 + "mt-2 sm:mt-0", 122 + className 123 + )} 124 + {...props} 125 + /> 126 + )) 127 + AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName 128 + 129 + export { 130 + AlertDialog, 131 + AlertDialogPortal, 132 + AlertDialogOverlay, 133 + AlertDialogTrigger, 134 + AlertDialogContent, 135 + AlertDialogHeader, 136 + AlertDialogFooter, 137 + AlertDialogTitle, 138 + AlertDialogDescription, 139 + AlertDialogAction, 140 + AlertDialogCancel, 141 + }
+186 -20
src/pages/projects/[id]/edit.tsx
··· 2 2 import Link from "next/link"; 3 3 import { useRouter } from "next/router"; 4 4 import { useEffect, useState } from "react"; 5 + import { toast } from "sonner"; 5 6 6 7 import Project, { priorities, statuses } from "@/models/project"; 7 8 ··· 16 17 import { Badge } from "@/components/ui/badge"; 17 18 import { Button } from "@/components/ui/button"; 18 19 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 20 + import { 21 + Dialog, 22 + DialogClose, 23 + DialogContent, 24 + DialogDescription, 25 + DialogFooter, 26 + DialogHeader, 27 + DialogTitle, 28 + DialogTrigger, 29 + } from "@/components/ui/dialog"; 30 + import { 31 + DropdownMenu, 32 + DropdownMenuContent, 33 + DropdownMenuItem, 34 + DropdownMenuLabel, 35 + DropdownMenuSeparator, 36 + DropdownMenuTrigger, 37 + } from "@/components/ui/dropdown-menu"; 19 38 import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"; 20 39 import { Input } from "@/components/ui/input"; 21 40 import { Label } from "@/components/ui/label"; ··· 31 50 const [projectStatus, setProjectStatus] = useState<string>(""); 32 51 const [projectPriority, setProjectPriority] = useState<string>(""); 33 52 53 + const [otherActionsDropdownIsOpen, setOtherActionsDropdownIsOpen] = useState<boolean>(false); 54 + const [deleteDialogIsOpen, setDeleteDialogIsOpen] = useState<boolean>(false); 55 + const [archiveDialogIsOpen, setArchiveDialogIsOpen] = useState<boolean>(false); 56 + 57 + const [otherActionsDropdownMobileIsOpen, setOtherActionsDropdownMobileIsOpen] = useState<boolean>(false); 58 + const [deleteDialogMobileIsOpen, setDeleteDialogMobileIsOpen] = useState<boolean>(false); 59 + const [archiveDialogMobileIsOpen, setArchiveDialogMobileIsOpen] = useState<boolean>(false); 60 + 34 61 useEffect(() => { 35 62 if (user) { 36 63 const foundProject = user.projects.find((project) => project.id === projectId); ··· 40 67 } 41 68 }, [user, projectId]); 42 69 70 + useEffect(() => { 71 + if (!deleteDialogIsOpen) { 72 + return () => { 73 + document.body.style.pointerEvents = ""; 74 + }; 75 + } 76 + }, [deleteDialogIsOpen]); 77 + 78 + useEffect(() => { 79 + if (!archiveDialogIsOpen) { 80 + return () => { 81 + document.body.style.pointerEvents = ""; 82 + }; 83 + } 84 + }, [archiveDialogIsOpen]); 85 + 86 + useEffect(() => { 87 + if (!deleteDialogMobileIsOpen) { 88 + return () => { 89 + document.body.style.pointerEvents = ""; 90 + }; 91 + } 92 + }, [deleteDialogMobileIsOpen]); 93 + 94 + useEffect(() => { 95 + if (!archiveDialogMobileIsOpen) { 96 + return () => { 97 + document.body.style.pointerEvents = ""; 98 + }; 99 + } 100 + }, [archiveDialogMobileIsOpen]); 101 + 43 102 const handleInputChange = (field: keyof Project, value: any) => { 44 103 setProject((prevProject) => (prevProject ? { ...prevProject, [field]: value } : null)); 45 104 }; ··· 59 118 } 60 119 }; 61 120 62 - const archiveProject = () => { 121 + const handleDeleteProject = () => { 122 + if (project && user) { 123 + const updatedUser = { ...user }; 124 + updatedUser.projects = updatedUser.projects.filter((p) => p.id !== project.id); 125 + 126 + setUser(updatedUser); 127 + saveData(updatedUser); 128 + router.push("/projects"); 129 + } 130 + }; 131 + 132 + const handleArchiveProject = () => { 63 133 if (user && project) { 64 134 project.archivedAt = new Date(); 65 135 ··· 69 139 if (session.end === null) session.end = new Date(); 70 140 }); 71 141 }); 142 + toast("We automatically stopped all sessions running."); 72 143 } 73 144 74 145 if (user.settings.automation.archiveProjectStatusRetirement) { 75 146 project.status = "completed"; 147 + toast("We automatically moved your project status to “Completed”."); 76 148 } 77 149 78 150 if (user.settings.automation.archiveTaskStatusRetirement) { 79 151 project.tasks.forEach((task) => { 80 152 task.status = "done"; 81 153 }); 154 + toast("We automatically moved every task of the project in “Done”."); 82 155 } 83 156 84 157 const updatedProjects = user.projects.map((proj) => (proj.id === project.id ? project : proj)); ··· 141 214 <StatusIconLabel statusValue={project.status} className="text-muted-foreground" /> 142 215 </Badge> 143 216 <div className="flex max-md:hidden items-center gap-2 md:ml-auto"> 217 + <DropdownMenu 218 + open={otherActionsDropdownIsOpen || archiveDialogIsOpen || deleteDialogIsOpen} 219 + onOpenChange={setOtherActionsDropdownIsOpen} 220 + > 221 + <DropdownMenuTrigger asChild> 222 + <Button variant="outline" size={"sm"}> 223 + Other Actions 224 + </Button> 225 + </DropdownMenuTrigger> 226 + <DropdownMenuContent className="w-56"> 227 + <DropdownMenuItem> 228 + <Dialog open={archiveDialogIsOpen} onOpenChange={setArchiveDialogIsOpen}> 229 + <DialogTrigger className="w-full text-left">Archive Project</DialogTrigger> 230 + <DialogContent> 231 + <DialogHeader> 232 + <DialogTitle>Are you absolutely sure?</DialogTitle> 233 + <DialogDescription> 234 + This action cannot be undone. This will permanently delete all 235 + your projects and remove this data from your local storage. 236 + </DialogDescription> 237 + </DialogHeader> 238 + <DialogFooter> 239 + <DialogClose> 240 + <Button variant={"outline"}>Cancel</Button> 241 + </DialogClose> 242 + <Button onClick={handleArchiveProject}>Continue</Button> 243 + </DialogFooter> 244 + </DialogContent> 245 + </Dialog> 246 + </DropdownMenuItem> 247 + <DropdownMenuItem> 248 + <Dialog open={deleteDialogIsOpen} onOpenChange={setDeleteDialogIsOpen}> 249 + <DialogTrigger className="w-full text-left">Delete Project</DialogTrigger> 250 + <DialogContent> 251 + <DialogHeader> 252 + <DialogTitle>Are you absolutely sure?</DialogTitle> 253 + <DialogDescription> 254 + This action cannot be undone. This will permanently delete your 255 + project {project.name} and remove this data from your local 256 + storage. 257 + </DialogDescription> 258 + </DialogHeader> 259 + <DialogFooter> 260 + <DialogClose> 261 + <Button variant={"outline"}>Cancel</Button> 262 + </DialogClose> 263 + <Button onClick={handleDeleteProject}>Continue</Button> 264 + </DialogFooter> 265 + </DialogContent> 266 + </Dialog> 267 + </DropdownMenuItem> 268 + </DropdownMenuContent> 269 + </DropdownMenu> 144 270 <Link href={`/projects/${project.id}`} className="text-muted-foreground"> 145 271 <Button variant="outline" size="sm"> 146 272 Discard ··· 279 405 </div> 280 406 </CardContent> 281 407 </Card> 282 - <Card x-chunk="dashboard-07-chunk-3"> 283 - <CardHeader> 284 - <CardTitle>Archive Project</CardTitle> 285 - </CardHeader> 286 - <CardContent> 287 - <div className="grid gap-6"> 288 - <div className="grid gap-3"> 289 - <p> 290 - The archive is a place where no properties can be edited any more. You 291 - can still view the project and unarchive it again, but it will be 292 - locked. Archived projects will not be visible in the projects list. 293 - </p> 294 - <Button onClick={archiveProject} variant="destructive" size="sm"> 295 - Archive 296 - </Button> 297 - </div> 298 - </div> 299 - </CardContent> 300 - </Card> 301 408 </div> 302 409 </div> 303 410 <div className="flex items-center justify-end gap-2 md:hidden"> 411 + <DropdownMenu 412 + open={ 413 + otherActionsDropdownMobileIsOpen || 414 + archiveDialogMobileIsOpen || 415 + deleteDialogMobileIsOpen 416 + } 417 + onOpenChange={setOtherActionsDropdownMobileIsOpen} 418 + > 419 + <DropdownMenuTrigger asChild> 420 + <Button variant="outline" size={"sm"}> 421 + Other Actions 422 + </Button> 423 + </DropdownMenuTrigger> 424 + <DropdownMenuContent className="w-56"> 425 + <DropdownMenuItem> 426 + <Dialog 427 + open={archiveDialogMobileIsOpen} 428 + onOpenChange={setArchiveDialogMobileIsOpen} 429 + > 430 + <DialogTrigger className="w-full text-left">Archive</DialogTrigger> 431 + <DialogContent> 432 + <DialogHeader> 433 + <DialogTitle>Are you absolutely sure?</DialogTitle> 434 + <DialogDescription> 435 + This action cannot be undone. This will permanently delete all your 436 + projects and remove this data from your local storage. 437 + </DialogDescription> 438 + </DialogHeader> 439 + <DialogFooter> 440 + <DialogClose> 441 + <Button variant={"outline"}>Cancel</Button> 442 + </DialogClose> 443 + <Button onClick={handleArchiveProject}>Continue</Button> 444 + </DialogFooter> 445 + </DialogContent> 446 + </Dialog> 447 + </DropdownMenuItem> 448 + <DropdownMenuItem> 449 + <Dialog open={deleteDialogMobileIsOpen} onOpenChange={setDeleteDialogMobileIsOpen}> 450 + <DialogTrigger className="w-full text-left">Delete</DialogTrigger> 451 + <DialogContent> 452 + <DialogHeader> 453 + <DialogTitle>Are you absolutely sure?</DialogTitle> 454 + <DialogDescription> 455 + This action cannot be undone. This will permanently delete your 456 + project {project.name} and remove this data from your local storage. 457 + </DialogDescription> 458 + </DialogHeader> 459 + <DialogFooter> 460 + <DialogClose> 461 + <Button variant={"outline"}>Cancel</Button> 462 + </DialogClose> 463 + <Button onClick={handleDeleteProject}>Continue</Button> 464 + </DialogFooter> 465 + </DialogContent> 466 + </Dialog> 467 + </DropdownMenuItem> 468 + </DropdownMenuContent> 469 + </DropdownMenu> 304 470 <Link href={`/projects/${project.id}`} className="text-muted-foreground"> 305 471 <Button variant="outline" size="sm"> 306 472 Discard
+74
src/pages/sessions/[id]/edit.tsx
··· 18 18 import { Badge } from "@/components/ui/badge"; 19 19 import { Button } from "@/components/ui/button"; 20 20 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 21 + import { 22 + Dialog, 23 + DialogClose, 24 + DialogContent, 25 + DialogDescription, 26 + DialogFooter, 27 + DialogHeader, 28 + DialogTitle, 29 + DialogTrigger, 30 + } from "@/components/ui/dialog"; 21 31 import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"; 22 32 import { Input } from "@/components/ui/input"; 23 33 import { Label } from "@/components/ui/label"; ··· 30 40 const router = useRouter(); 31 41 32 42 const [sessionFlow, setSessionFlow] = useState<string>(""); 43 + 44 + const [deleteDialogIsOpen, setDeleteDialogIsOpen] = useState<boolean>(false); 45 + const [deleteDialogMobileIsOpen, setDeleteDialogMobileIsOpen] = useState<boolean>(false); 33 46 34 47 useEffect(() => { 35 48 if (user) { ··· 63 76 } 64 77 }; 65 78 79 + const handleDeleteSession = () => { 80 + if (session && user) { 81 + const updatedUser = { ...user }; 82 + updatedUser.projects = updatedUser.projects.map((project) => { 83 + const updatedTasks = project.tasks.map((task) => { 84 + const updatedSessions = task.sessions.filter((s) => s.id !== session.id); 85 + return { ...task, sessions: updatedSessions }; 86 + }); 87 + return { ...project, tasks: updatedTasks }; 88 + }); 89 + 90 + setUser(updatedUser); 91 + saveData(updatedUser); 92 + router.push("/sessions"); 93 + } 94 + }; 95 + 66 96 if (!session) { 67 97 return ( 68 98 <div className="flex w-full flex-col"> ··· 124 154 </Badge> 125 155 )} 126 156 <div className="flex items-center gap-2 md:ml-auto max-md:hidden"> 157 + <Dialog open={deleteDialogIsOpen} onOpenChange={setDeleteDialogIsOpen}> 158 + <DialogTrigger asChild> 159 + <Button variant={"destructive"} size={"sm"}> 160 + Delete 161 + </Button> 162 + </DialogTrigger> 163 + <DialogContent> 164 + <DialogHeader> 165 + <DialogTitle>Are you absolutely sure?</DialogTitle> 166 + <DialogDescription> 167 + This action cannot be undone. This will permanently delete this session and 168 + remove this data from your local storage. 169 + </DialogDescription> 170 + </DialogHeader> 171 + <DialogFooter> 172 + <DialogClose> 173 + <Button variant={"outline"}>Cancel</Button> 174 + </DialogClose> 175 + <Button onClick={handleDeleteSession}>Continue</Button> 176 + </DialogFooter> 177 + </DialogContent> 178 + </Dialog> 127 179 <Link href={`/sessions/${session.id}`} className="text-muted-foreground"> 128 180 <Button variant="outline" size="sm"> 129 181 Discard ··· 231 283 </div> 232 284 </div> 233 285 <div className="flex items-center justify-end gap-2 md:hidden"> 286 + <Dialog open={deleteDialogMobileIsOpen} onOpenChange={setDeleteDialogMobileIsOpen}> 287 + <DialogTrigger asChild> 288 + <Button variant={"destructive"} size={"sm"}> 289 + Delete 290 + </Button> 291 + </DialogTrigger> 292 + <DialogContent> 293 + <DialogHeader> 294 + <DialogTitle>Are you absolutely sure?</DialogTitle> 295 + <DialogDescription> 296 + This action cannot be undone. This will permanently delete this session and 297 + remove this data from your local storage. 298 + </DialogDescription> 299 + </DialogHeader> 300 + <DialogFooter> 301 + <DialogClose> 302 + <Button variant={"outline"}>Cancel</Button> 303 + </DialogClose> 304 + <Button onClick={handleDeleteSession}>Continue</Button> 305 + </DialogFooter> 306 + </DialogContent> 307 + </Dialog> 234 308 <Link href={`/sessions/${session.id}`} className="text-muted-foreground"> 235 309 <Button variant="outline" size="sm"> 236 310 Discard
+71
src/pages/tasks/[id]/edit.tsx
··· 18 18 import { Badge } from "@/components/ui/badge"; 19 19 import { Button } from "@/components/ui/button"; 20 20 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 21 + import { 22 + Dialog, 23 + DialogClose, 24 + DialogContent, 25 + DialogDescription, 26 + DialogFooter, 27 + DialogHeader, 28 + DialogTitle, 29 + DialogTrigger, 30 + } from "@/components/ui/dialog"; 21 31 import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"; 22 32 import { Input } from "@/components/ui/input"; 23 33 import { Label } from "@/components/ui/label"; ··· 32 42 33 43 const [taskStatus, setTaskStatus] = useState<string>(""); 34 44 const [taskPriority, setTaskPriority] = useState<string>(""); 45 + 46 + const [deleteDialogIsOpen, setDeleteDialogIsOpen] = useState<boolean>(false); 47 + const [deleteDialogMobileIsOpen, setDeleteDialogMobileIsOpen] = useState<boolean>(false); 35 48 36 49 useEffect(() => { 37 50 if (user) { ··· 62 75 } 63 76 }; 64 77 78 + const handleDeleteTask = () => { 79 + if (task && user) { 80 + const updatedUser = { ...user }; 81 + updatedUser.projects = updatedUser.projects.map((project) => { 82 + const updatedTasks = project.tasks.filter((t) => t.id !== task.id); 83 + return { ...project, tasks: updatedTasks }; 84 + }); 85 + 86 + setUser(updatedUser); 87 + saveData(updatedUser); 88 + router.push("/tasks"); 89 + } 90 + }; 91 + 65 92 if (!task) { 66 93 return ( 67 94 <div className="flex w-full flex-col"> ··· 120 147 </Badge> 121 148 )} 122 149 <div className="flex items-center gap-2 md:ml-auto max-md:hidden"> 150 + <Dialog open={deleteDialogIsOpen} onOpenChange={setDeleteDialogIsOpen}> 151 + <DialogTrigger asChild> 152 + <Button variant={"destructive"} size={"sm"}> 153 + Delete 154 + </Button> 155 + </DialogTrigger> 156 + <DialogContent> 157 + <DialogHeader> 158 + <DialogTitle>Are you absolutely sure?</DialogTitle> 159 + <DialogDescription> 160 + This action cannot be undone. This will permanently delete your task{" "} 161 + {task.name} and remove this data from your local storage. 162 + </DialogDescription> 163 + </DialogHeader> 164 + <DialogFooter> 165 + <DialogClose> 166 + <Button variant={"outline"}>Cancel</Button> 167 + </DialogClose> 168 + <Button onClick={handleDeleteTask}>Continue</Button> 169 + </DialogFooter> 170 + </DialogContent> 171 + </Dialog> 123 172 <Link href={`/tasks/${task.id}`} className="text-muted-foreground"> 124 173 <Button variant="outline" size="sm"> 125 174 Discard ··· 261 310 </div> 262 311 </div> 263 312 <div className="flex items-center justify-end gap-2 md:hidden"> 313 + <Dialog open={deleteDialogMobileIsOpen} onOpenChange={setDeleteDialogMobileIsOpen}> 314 + <DialogTrigger asChild> 315 + <Button variant={"destructive"} size={"sm"}> 316 + Delete 317 + </Button> 318 + </DialogTrigger> 319 + <DialogContent> 320 + <DialogHeader> 321 + <DialogTitle>Are you absolutely sure?</DialogTitle> 322 + <DialogDescription> 323 + This action cannot be undone. This will permanently delete your task {task.name}{" "} 324 + and remove this data from your local storage. 325 + </DialogDescription> 326 + </DialogHeader> 327 + <DialogFooter> 328 + <DialogClose> 329 + <Button variant={"outline"}>Cancel</Button> 330 + </DialogClose> 331 + <Button onClick={handleDeleteTask}>Continue</Button> 332 + </DialogFooter> 333 + </DialogContent> 334 + </Dialog> 264 335 <Link href={`/tasks/${task.id}`} className="text-muted-foreground"> 265 336 <Button variant="outline" size="sm"> 266 337 Discard