From 30afe78ca2ef5ddd8840aaec40a711fd7afad0eb Mon Sep 17 00:00:00 2001 From: Yehoshua Sandler Date: Fri, 23 May 2025 19:52:50 -0500 Subject: [PATCH] feat: update workout --- package-lock.json | 357 ++++++++++++++++-- package.json | 9 +- .../[tenant]/dashboard/workouts/layout.tsx | 2 +- src/app/(frontend)/[tenant]/layout.tsx | 2 + .../Workouts/WorkoutPopoutEditForm.tsx | 145 ++++--- src/components/Workouts/WorkoutsListView.tsx | 12 +- src/components/ui/badge.tsx | 36 ++ src/components/ui/command.tsx | 153 ++++++++ src/components/ui/dialog.tsx | 122 ++++++ src/components/ui/multi-select.tsx | 341 +++++++++++++++++ src/components/ui/popover.tsx | 267 ++----------- src/components/ui/separator.tsx | 2 +- src/components/ui/sonner.tsx | 31 ++ src/serverActions/UseServer.ts | 7 + src/serverActions/updateWorkout.ts | 30 ++ 15 files changed, 1167 insertions(+), 349 deletions(-) create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/command.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/multi-select.tsx create mode 100644 src/components/ui/sonner.tsx create mode 100644 src/serverActions/UseServer.ts create mode 100644 src/serverActions/updateWorkout.ts diff --git a/package-lock.json b/package-lock.json index 850ad21..ff2e6f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,17 +26,18 @@ "@radix-ui/react-avatar": "^1.1.9", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-collapsible": "^1.1.10", - "@radix-ui/react-dialog": "^1.1.13", + "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dropdown-menu": "^2.1.14", "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-popover": "^1.1.13", + "@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-select": "^2.0.0", - "@radix-ui/react-separator": "^1.1.6", + "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tooltip": "^1.2.6", "@tabler/icons-react": "^3.33.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "cross-env": "^7.0.3", "date-fns": "^4.1.0", "framer-motion": "^12.12.1", @@ -46,6 +47,7 @@ "motion": "^12.12.1", "next": "15.3.0", "next-sitemap": "^4.2.3", + "next-themes": "^0.4.6", "payload": "3.38.0", "prism-react-renderer": "^2.3.1", "react": "19.1.0", @@ -54,6 +56,7 @@ "react-hook-form": "^7.45.4", "react-use-measure": "^2.1.7", "sharp": "0.32.6", + "sonner": "^2.0.3", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "zod": "^3.25.7", @@ -3932,6 +3935,16 @@ "react-dom": "^19.0.0 || ^19.0.0-rc-65a56d0e-20241020" } }, + "node_modules/@payloadcms/ui/node_modules/sonner": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz", + "integrity": "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -4140,22 +4153,22 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.13.tgz", - "integrity": "sha512-ARFmqUyhIVS3+riWzwGTe7JLjqwqgnODBUZdqpWar/z1WFs9z76fuOs/2BOWCR+YboRn4/WN9aoaGVwqNRr8VA==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz", + "integrity": "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.9", + "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", - "@radix-ui/react-focus-scope": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-portal": "1.1.8", + "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", - "@radix-ui/react-primitive": "2.1.2", - "@radix-ui/react-slot": "1.2.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" @@ -4175,21 +4188,102 @@ } } }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.2.tgz", - "integrity": "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==", + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true } } }, @@ -4427,23 +4521,23 @@ } }, "node_modules/@radix-ui/react-popover": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.13.tgz", - "integrity": "sha512-84uqQV3omKDR076izYgcha6gdpN8m3z6w/AeJ83MSBJYVG/AbOHdLjAgsPZkeC/kt+k64moXFCnio8BbqXszlw==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.14.tgz", + "integrity": "sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.9", + "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", - "@radix-ui/react-focus-scope": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.6", - "@radix-ui/react-portal": "1.1.8", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", - "@radix-ui/react-primitive": "2.1.2", - "@radix-ui/react-slot": "1.2.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" @@ -4463,21 +4557,157 @@ } } }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.2.tgz", - "integrity": "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==", + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" + "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", + "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true } } }, @@ -4695,12 +4925,35 @@ } }, "node_modules/@radix-ui/react-separator": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.6.tgz", - "integrity": "sha512-Izof3lPpbCfTM7WDta+LRkz31jem890VjEvpVRoWQNKpDUMMVffuyq854XPGP1KYGWWmjmYvHvPFeocWhFCy1w==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.2" + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -7426,6 +7679,22 @@ "node": ">=6" } }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -11417,6 +11686,16 @@ "integrity": "sha512-fbb2C7HChgM7CemdCY+y3N1n8pcTKdqtQLbC7/EQtPdLvlMUT9JX/dBYl8MMZAtYG4uVMyPFHXckb68q/NRwqg==", "license": "MIT" }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -13624,9 +13903,9 @@ } }, "node_modules/sonner": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz", - "integrity": "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz", + "integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==", "license": "MIT", "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", diff --git a/package.json b/package.json index 5044aa9..431ab92 100644 --- a/package.json +++ b/package.json @@ -36,17 +36,18 @@ "@radix-ui/react-avatar": "^1.1.9", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-collapsible": "^1.1.10", - "@radix-ui/react-dialog": "^1.1.13", + "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dropdown-menu": "^2.1.14", "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-popover": "^1.1.13", + "@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-select": "^2.0.0", - "@radix-ui/react-separator": "^1.1.6", + "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tooltip": "^1.2.6", "@tabler/icons-react": "^3.33.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "cross-env": "^7.0.3", "date-fns": "^4.1.0", "framer-motion": "^12.12.1", @@ -56,6 +57,7 @@ "motion": "^12.12.1", "next": "15.3.0", "next-sitemap": "^4.2.3", + "next-themes": "^0.4.6", "payload": "3.38.0", "prism-react-renderer": "^2.3.1", "react": "19.1.0", @@ -64,6 +66,7 @@ "react-hook-form": "^7.45.4", "react-use-measure": "^2.1.7", "sharp": "0.32.6", + "sonner": "^2.0.3", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "zod": "^3.25.7", diff --git a/src/app/(frontend)/[tenant]/dashboard/workouts/layout.tsx b/src/app/(frontend)/[tenant]/dashboard/workouts/layout.tsx index d9bb456..5644662 100644 --- a/src/app/(frontend)/[tenant]/dashboard/workouts/layout.tsx +++ b/src/app/(frontend)/[tenant]/dashboard/workouts/layout.tsx @@ -72,7 +72,7 @@ const WorkoutsLayout = async (props: Props) => { const getWorkoutsPromise = payload.find({ collection: 'workouts', limit: 20, - depth: 0, + depth: 1, select: { id: true, name: true, diff --git a/src/app/(frontend)/[tenant]/layout.tsx b/src/app/(frontend)/[tenant]/layout.tsx index a91eb5e..c6c664d 100644 --- a/src/app/(frontend)/[tenant]/layout.tsx +++ b/src/app/(frontend)/[tenant]/layout.tsx @@ -4,6 +4,7 @@ import { headers as getHeaders } from 'next/headers.js' import { ReactNode, Suspense } from 'react' import RootLayoutSuspenseFrontend from './layout.client' import { Tenant } from '@/payload-types' +import { Toaster } from '@/components/ui/sonner' export const metadata = { title: 'Biotracker', @@ -50,6 +51,7 @@ const RootLayoutFrontend = async (props: Props) => { getTenantPromise={getTenantPromise} > {children} + diff --git a/src/components/Workouts/WorkoutPopoutEditForm.tsx b/src/components/Workouts/WorkoutPopoutEditForm.tsx index de8b1f3..e8d08db 100644 --- a/src/components/Workouts/WorkoutPopoutEditForm.tsx +++ b/src/components/Workouts/WorkoutPopoutEditForm.tsx @@ -1,83 +1,130 @@ 'use client' -import { Workout } from '@/payload-types' + +import { Workout, WorkoutType } from '@/payload-types' import useWorkouts from '@/stores/Workouts' -import { - PopoverBody, - PopoverContent, - PopoverHeader, - PopoverRoot, - PopoverTrigger, -} from '../ui/popover' -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from '../ui/form' + +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../ui/form' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { Input } from '../ui/input' import { z } from 'zod' +import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialog' +import { DialogTitle } from '@radix-ui/react-dialog' +import { Edit, Loader2 } from 'lucide-react' +import { MultiSelect } from '../ui/multi-select' +import { Button } from '../ui/button' +import updateWorkout from '@/serverActions/updateWorkout' +import { useMemo, useState } from 'react' +import { toast } from 'sonner' const editFormSchema = z.object({ name: z.string().min(2, { message: 'Workout name must be at least 2 characters.', }), + workoutTypes: z.array(z.number()).nonempty('Workout needs to have at least one type.'), }) type Props = { workout: Workout } const WorkoutPopoutEditForm = (props: Props) => { - const w = props.workout + const { workout: w } = props + const { workoutTypes } = useWorkouts() + const [isSaving, setIsSaving] = useState(false) + + const initialSelectedTypes = useMemo(() => w.type?.map((t) => (t as WorkoutType).id) || [], []) + const form = useForm>({ resolver: zodResolver(editFormSchema), defaultValues: { - name: '', + name: w.name || '', + workoutTypes: initialSelectedTypes, }, }) - const handleEditSubmit = (e: any) => { - console.log(e) + const handleEditSubmit = async (values: z.infer) => { + if (isSaving) return + setIsSaving(true) + + try { + const updateWorkoutResponse = await updateWorkout({ + id: w.id, + updatedProps: { + name: values.name, + type: values.workoutTypes, + }, + }) + + if (updateWorkoutResponse) toast('Workout Updated') + else toast('Issue Updating Workout') + } catch (err) { + console.error(err) + toast('Issue Updating Workout') + } finally { + setIsSaving(false) + } } return ( - - -
- {w.name} - - Edit - -
-
- - Edit Workout - -
- - ( + + + + + + Edit Workout + + + + ( + + Workout Name + + + + + + )} + /> + { + const workoutTypeOptions = + workoutTypes?.docs.map((t) => ({ + value: t.id, + label: t.name || '', + })) || [] + + return ( - Workout Name - + {/* @ts-expect-error Some stupid type stuff from this component cause I use number as the value. Haven't seen an issue yet. */} + - This is your public display name. - )} - /> - - -
-
-
+ ) + }} + /> + + + + + + ) } diff --git a/src/components/Workouts/WorkoutsListView.tsx b/src/components/Workouts/WorkoutsListView.tsx index 538548d..08d4b1f 100644 --- a/src/components/Workouts/WorkoutsListView.tsx +++ b/src/components/Workouts/WorkoutsListView.tsx @@ -1,15 +1,12 @@ 'use client' import useWorkouts from '@/stores/Workouts' -import NeumorphButton from '../ui/neumorph-button' import WorkoutPopoutEditForm from './WorkoutPopoutEditForm' import { Media, Workout } from '@/payload-types' import { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar' import makeAcronym from '@/utilities/makeAcronym' import Link from 'next/link' import { useCallback } from 'react' -import { ChevronLeftIcon, ChevronRightIcon, Edit } from 'lucide-react' -import { Button } from '../ui/button' const WorkoutListView = () => { const { workouts, workoutTypes } = useWorkouts() @@ -63,7 +60,6 @@ const WorkoutListView = () => {

{w.type ?.map((t) => { - console.log(t) if (typeof t === 'number') return getWorkoutTypeById(t)?.name else return t.name }) @@ -76,13 +72,7 @@ const WorkoutListView = () => {

)} - + ) diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..2a4ac09 --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils/index" + +const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx new file mode 100644 index 0000000..97ed218 --- /dev/null +++ b/src/components/ui/command.tsx @@ -0,0 +1,153 @@ +"use client" + +import * as React from "react" +import { type DialogProps } from "@radix-ui/react-dialog" +import { Command as CommandPrimitive } from "cmdk" +import { Search } from "lucide-react" + +import { cn } from "@/lib/utils/index" +import { Dialog, DialogContent } from "@/components/ui/dialog" + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Command.displayName = CommandPrimitive.displayName + +const CommandDialog = ({ children, ...props }: DialogProps) => { + return ( + + + + {children} + + + + ) +} + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)) + +CommandInput.displayName = CommandPrimitive.Input.displayName + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandList.displayName = CommandPrimitive.List.displayName + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)) + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandGroup.displayName = CommandPrimitive.Group.displayName + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +CommandSeparator.displayName = CommandPrimitive.Separator.displayName + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandItem.displayName = CommandPrimitive.Item.displayName + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +CommandShortcut.displayName = "CommandShortcut" + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..0dfd9fd --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils/index" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogTrigger, + DialogClose, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/src/components/ui/multi-select.tsx b/src/components/ui/multi-select.tsx new file mode 100644 index 0000000..f46bb20 --- /dev/null +++ b/src/components/ui/multi-select.tsx @@ -0,0 +1,341 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' +import { CheckIcon, XCircle, ChevronDown, XIcon, WandSparkles } from 'lucide-react' + +import { cn } from '@/lib/utils' +import { Separator } from '@/components/ui/separator' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, +} from '@/components/ui/command' + +/** + * Variants for the multi-select component to handle different styles. + * Uses class-variance-authority (cva) to define different styles based on "variant" prop. + */ +const multiSelectVariants = cva( + 'm-1 transition ease-in-out delay-150 hover:-translate-y-1 hover:scale-110 duration-300', + { + variants: { + variant: { + default: 'border-foreground/10 text-foreground bg-card hover:bg-card/80', + secondary: + 'border-foreground/10 bg-secondary text-secondary-foreground hover:bg-secondary/80', + destructive: + 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', + inverted: 'inverted', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +/** + * Props for MultiSelect component + */ +interface MultiSelectProps + extends React.ButtonHTMLAttributes, + VariantProps { + /** + * An array of option objects to be displayed in the multi-select component. + * Each option object has a label, value, and an optional icon. + */ + options: { + /** The text to display for the option. */ + label: string + /** The unique value associated with the option. */ + value: any + /** Optional icon component to display alongside the option. */ + icon?: React.ComponentType<{ className?: string }> + }[] + + /** + * Callback function triggered when the selected values change. + * Receives an array of the new selected values. + */ + onValueChange: (value: any[]) => void + + /** The default selected values when the component mounts. */ + defaultValue?: any[] + + /** + * Placeholder text to be displayed when no values are selected. + * Optional, defaults to "Select options". + */ + placeholder?: string + + /** + * Animation duration in seconds for the visual effects (e.g., bouncing badges). + * Optional, defaults to 0 (no animation). + */ + animation?: number + + /** + * Maximum number of items to display. Extra selected items will be summarized. + * Optional, defaults to 3. + */ + maxCount?: number + + /** + * The modality of the popover. When set to true, interaction with outside elements + * will be disabled and only popover content will be visible to screen readers. + * Optional, defaults to false. + */ + modalPopover?: boolean + + /** + * If true, renders the multi-select component as a child of another component. + * Optional, defaults to false. + */ + asChild?: boolean + + /** + * Additional class names to apply custom styles to the multi-select component. + * Optional, can be used to add custom styles. + */ + className?: string +} + +export const MultiSelect = React.forwardRef( + ( + { + options, + onValueChange, + variant, + defaultValue = [], + placeholder = 'Select options', + animation = 0, + maxCount = 3, + modalPopover = false, + asChild = false, + className, + ...props + }, + ref, + ) => { + const [selectedValues, setSelectedValues] = React.useState(defaultValue) + const [isPopoverOpen, setIsPopoverOpen] = React.useState(false) + const [isAnimating, setIsAnimating] = React.useState(false) + + const handleInputKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + setIsPopoverOpen(true) + } else if (event.key === 'Backspace' && !event.currentTarget.value) { + const newSelectedValues = [...selectedValues] + newSelectedValues.pop() + setSelectedValues(newSelectedValues) + onValueChange(newSelectedValues) + } + } + + const toggleOption = (option: string) => { + const newSelectedValues = selectedValues.includes(option) + ? selectedValues.filter((value) => value !== option) + : [...selectedValues, option] + setSelectedValues(newSelectedValues) + onValueChange(newSelectedValues) + } + + const handleClear = () => { + setSelectedValues([]) + onValueChange([]) + } + + const handleTogglePopover = () => { + setIsPopoverOpen((prev) => !prev) + } + + const clearExtraOptions = () => { + const newSelectedValues = selectedValues.slice(0, maxCount) + setSelectedValues(newSelectedValues) + onValueChange(newSelectedValues) + } + + const toggleAll = () => { + if (selectedValues.length === options.length) { + handleClear() + } else { + const allValues = options.map((option) => option.value) + setSelectedValues(allValues) + onValueChange(allValues) + } + } + + return ( + + + + + setIsPopoverOpen(false)} + > + + + + No results found. + + +
+ +
+ (Select All) +
+ {options.map((option) => { + const isSelected = selectedValues.includes(option.value) + return ( + toggleOption(option.value)} + className="cursor-pointer" + > +
+ +
+ {option.icon && ( + + )} + {option.label} +
+ ) + })} +
+ + +
+ {selectedValues.length > 0 && ( + <> + + Clear + + + + )} + setIsPopoverOpen(false)} + className="flex-1 justify-center cursor-pointer max-w-full" + > + Close + +
+
+
+
+
+ {animation > 0 && selectedValues.length > 0 && ( + setIsAnimating(!isAnimating)} + /> + )} +
+ ) + }, +) + +MultiSelect.displayName = 'MultiSelect' diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx index 411bc8a..328f8d2 100644 --- a/src/components/ui/popover.tsx +++ b/src/components/ui/popover.tsx @@ -1,254 +1,31 @@ 'use client' -import React, { - createContext, - HTMLAttributes, - useContext, - useEffect, - useId, - useRef, - useState, -} from 'react' -import { X } from 'lucide-react' -import { AnimatePresence, MotionConfig, motion } from 'motion/react' +import * as React from 'react' +import * as PopoverPrimitive from '@radix-ui/react-popover' import { cn } from '@/lib/utils/index' -const TRANSITION = { - type: 'spring', - bounce: 0.05, - duration: 0.3, -} +const Popover = PopoverPrimitive.Root -function useClickOutside(ref: React.RefObject, handler: () => void) { - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (ref.current && !ref.current.contains(event.target as Node)) { - handler() - } - } +const PopoverTrigger = PopoverPrimitive.Trigger - document.addEventListener('mousedown', handleClickOutside) - return () => { - document.removeEventListener('mousedown', handleClickOutside) - } - }, [ref, handler]) -} +const PopoverAnchor = PopoverPrimitive.Anchor -interface PopoverContextType { - isOpen: boolean - openPopover: () => void - closePopover: () => void - uniqueId: string - note: string - setNote: (note: string) => void -} +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => ( + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName -const PopoverContext = createContext(undefined) - -function usePopover() { - const context = useContext(PopoverContext) - if (!context) { - throw new Error('usePopover must be used within a PopoverProvider') - } - return context -} - -function usePopoverLogic() { - const uniqueId = useId() - const [isOpen, setIsOpen] = useState(false) - const [note, setNote] = useState('') - - const openPopover = () => setIsOpen(true) - const closePopover = () => { - setIsOpen(false) - setNote('') - } - - return { isOpen, openPopover, closePopover, uniqueId, note, setNote } -} - -interface PopoverRootProps { - children: React.ReactNode - className?: string -} - -export function PopoverRoot({ children, className }: PopoverRootProps) { - const popoverLogic = usePopoverLogic() - - return ( - - -
- {children} -
-
-
- ) -} - -interface PopoverTriggerProps { - children: React.ReactNode - className?: string -} - -export function PopoverTrigger({ children, className }: PopoverTriggerProps) { - const { openPopover, uniqueId } = usePopover() - - return ( - - {children} - - ) -} - -interface PopoverContentProps { - children: React.ReactNode - className?: string -} - -export function PopoverContent({ children, className }: PopoverContentProps) { - const { isOpen, closePopover, uniqueId } = usePopover() - const formContainerRef = useRef(null) - - useClickOutside(formContainerRef, closePopover) - - useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Escape') { - closePopover() - } - } - - document.addEventListener('keydown', handleKeyDown) - - return () => { - document.removeEventListener('keydown', handleKeyDown) - } - }, [closePopover]) - - return ( - - {isOpen && ( - - {children} - - )} - - ) -} - -interface PopoverFooterProps { - children: React.ReactNode - className?: string -} - -export function PopoverFooter({ children, className }: PopoverFooterProps) { - return ( -
- {children} -
- ) -} - -interface PopoverCloseButtonProps { - className?: string -} - -export function PopoverCloseButton({ className }: PopoverCloseButtonProps) { - const { closePopover } = usePopover() - - return ( - - ) -} - -interface PopoverSubmitButtonProps { - className?: string -} - -export function PopoverSubmitButton({ className }: PopoverSubmitButtonProps) { - return ( - - ) -} - -export function PopoverHeader({ - children, - className, -}: { - children: React.ReactNode - className?: string -}) { - return ( -
- {children} -
- ) -} - -export function PopoverBody({ - children, - className, -}: { - children: React.ReactNode - className?: string -}) { - return
{children}
-} - -// New component: PopoverButton -export function PopoverButton({ - children, - onClick, - className, -}: { - children: React.ReactNode - onClick?: () => void - className?: string -}) { - return ( - - ) -} +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx index 12d81c4..c12e63c 100644 --- a/src/components/ui/separator.tsx +++ b/src/components/ui/separator.tsx @@ -3,7 +3,7 @@ import * as React from "react" import * as SeparatorPrimitive from "@radix-ui/react-separator" -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils/index" const Separator = React.forwardRef< React.ElementRef, diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx new file mode 100644 index 0000000..452f4d9 --- /dev/null +++ b/src/components/ui/sonner.tsx @@ -0,0 +1,31 @@ +"use client" + +import { useTheme } from "next-themes" +import { Toaster as Sonner } from "sonner" + +type ToasterProps = React.ComponentProps + +const Toaster = ({ ...props }: ToasterProps) => { + const { theme = "system" } = useTheme() + + return ( + + ) +} + +export { Toaster } diff --git a/src/serverActions/UseServer.ts b/src/serverActions/UseServer.ts new file mode 100644 index 0000000..21014b6 --- /dev/null +++ b/src/serverActions/UseServer.ts @@ -0,0 +1,7 @@ +'use server' + +import updateWorkout from "./updateWorkout" + +export { + updateWorkout +} diff --git a/src/serverActions/updateWorkout.ts b/src/serverActions/updateWorkout.ts new file mode 100644 index 0000000..f8c1921 --- /dev/null +++ b/src/serverActions/updateWorkout.ts @@ -0,0 +1,30 @@ +'use server' + +import { Workout } from '@/payload-types' +import { getPayload } from 'payload' +import config from '@/payload.config' + +type Props = { + id: number, + updatedProps: Partial +} +const updateWorkout = async (props: Props): Promise => { + const payloadConfig = await config + const payload = await getPayload({ config: payloadConfig }) + + const { id, updatedProps } = props + + try { + const updatedWorkout = await payload.update({ + collection: 'workouts', + id: id, + data: updatedProps + }) + return updatedWorkout + } catch (_) { + return null + } + +} + +export default updateWorkout