feat: workouts list view admin panel
This commit is contained in:
parent
0f5f6409cc
commit
4b7901703b
284
package-lock.json
generated
284
package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hookform/resolvers": "^5.0.1",
|
||||||
"@payloadcms/admin-bar": "3.38.0",
|
"@payloadcms/admin-bar": "3.38.0",
|
||||||
"@payloadcms/db-postgres": "3.38.0",
|
"@payloadcms/db-postgres": "3.38.0",
|
||||||
"@payloadcms/live-preview-react": "3.38.0",
|
"@payloadcms/live-preview-react": "3.38.0",
|
||||||
@ -27,20 +28,22 @@
|
|||||||
"@radix-ui/react-collapsible": "^1.1.10",
|
"@radix-ui/react-collapsible": "^1.1.10",
|
||||||
"@radix-ui/react-dialog": "^1.1.13",
|
"@radix-ui/react-dialog": "^1.1.13",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.14",
|
"@radix-ui/react-dropdown-menu": "^2.1.14",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-popover": "^1.1.13",
|
"@radix-ui/react-popover": "^1.1.13",
|
||||||
"@radix-ui/react-select": "^2.0.0",
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
"@radix-ui/react-separator": "^1.1.6",
|
"@radix-ui/react-separator": "^1.1.6",
|
||||||
"@radix-ui/react-slot": "^1.2.2",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@radix-ui/react-tooltip": "^1.2.6",
|
"@radix-ui/react-tooltip": "^1.2.6",
|
||||||
"@tabler/icons-react": "^3.33.0",
|
"@tabler/icons-react": "^3.33.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
"framer-motion": "^12.12.1",
|
||||||
"geist": "^1.3.0",
|
"geist": "^1.3.0",
|
||||||
"graphql": "^16.8.2",
|
"graphql": "^16.8.2",
|
||||||
"lucide-react": "^0.378.0",
|
"lucide-react": "^0.378.0",
|
||||||
|
"motion": "^12.12.1",
|
||||||
"next": "15.3.0",
|
"next": "15.3.0",
|
||||||
"next-sitemap": "^4.2.3",
|
"next-sitemap": "^4.2.3",
|
||||||
"payload": "3.38.0",
|
"payload": "3.38.0",
|
||||||
@ -48,10 +51,12 @@
|
|||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-hook-form": "7.45.4",
|
"react-hook-form": "^7.45.4",
|
||||||
|
"react-use-measure": "^2.1.7",
|
||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.3.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"zod": "^3.25.7",
|
||||||
"zustand": "^5.0.4"
|
"zustand": "^5.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -2493,6 +2498,18 @@
|
|||||||
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
|
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@hookform/resolvers": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@standard-schema/utils": "^0.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react-hook-form": "^7.55.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.1",
|
"version": "0.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||||
@ -4074,6 +4091,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-collection/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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-compose-refs": {
|
"node_modules/@radix-ui/react-compose-refs": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
||||||
@ -4140,6 +4175,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-direction": {
|
"node_modules/@radix-ui/react-direction": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
|
||||||
@ -4270,12 +4323,35 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-label": {
|
"node_modules/@radix-ui/react-label": {
|
||||||
"version": "2.1.6",
|
"version": "2.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz",
|
||||||
"integrity": "sha512-S/hv1mTlgcPX2gCTJrWuTjSXf7ER3Zf7zWGtOprxhIIY93Qin3n5VgNA0Ez9AgrK/lEtlYgzLd4f5x6AVar4Yw==",
|
"integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"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-label/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": {
|
"peerDependencies": {
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
@ -4332,6 +4408,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-menu/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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-popover": {
|
"node_modules/@radix-ui/react-popover": {
|
||||||
"version": "1.1.13",
|
"version": "1.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.13.tgz",
|
||||||
@ -4369,6 +4463,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-popper": {
|
"node_modules/@radix-ui/react-popper": {
|
||||||
"version": "1.2.6",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.6.tgz",
|
||||||
@ -4472,6 +4584,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-primitive/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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-roving-focus": {
|
"node_modules/@radix-ui/react-roving-focus": {
|
||||||
"version": "1.1.9",
|
"version": "1.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.9.tgz",
|
||||||
@ -4546,6 +4676,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-select/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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-separator": {
|
"node_modules/@radix-ui/react-separator": {
|
||||||
"version": "1.1.6",
|
"version": "1.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.6.tgz",
|
||||||
@ -4570,9 +4718,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-slot": {
|
"node_modules/@radix-ui/react-slot": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||||
"integrity": "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==",
|
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-compose-refs": "1.1.2"
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
@ -4621,6 +4769,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-tooltip/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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-use-callback-ref": {
|
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
|
||||||
@ -5549,6 +5715,12 @@
|
|||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@standard-schema/utils": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@swc/counter": {
|
"node_modules/@swc/counter": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||||
@ -8937,6 +9109,33 @@
|
|||||||
"url": "https://github.com/sponsors/rawify"
|
"url": "https://github.com/sponsors/rawify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "12.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.12.1.tgz",
|
||||||
|
"integrity": "sha512-PFw4/GCREHI2suK/NlPSUxd+x6Rkp80uQsfCRFSOQNrm5pZif7eGtmG1VaD/UF1fW9tRBy5AaS77StatB3OJDg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-dom": "^12.12.1",
|
||||||
|
"motion-utils": "^12.12.1",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fs-constants": {
|
"node_modules/fs-constants": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||||
@ -11025,6 +11224,47 @@
|
|||||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/motion": {
|
||||||
|
"version": "12.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion/-/motion-12.12.1.tgz",
|
||||||
|
"integrity": "sha512-vN/3p++Ix0lVt9NH0ZqPrAy8QRTkff27t5z3z5+4BJ3cXPxtOia2EBZS4snM+JUTfl7J0JXP/5ERqu9GT/8IgA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"framer-motion": "^12.12.1",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-dom": {
|
||||||
|
"version": "12.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.12.1.tgz",
|
||||||
|
"integrity": "sha512-GXq/uUbZBEiFFE+K1Z/sxdPdadMdfJ/jmBALDfIuHGi0NmtealLOfH9FqT+6aNPgVx8ilq0DtYmyQlo6Uj9LKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-utils": "^12.12.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-utils": {
|
||||||
|
"version": "12.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.12.1.tgz",
|
||||||
|
"integrity": "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@ -12773,6 +13013,21 @@
|
|||||||
"react-dom": ">=16.6.0"
|
"react-dom": ">=16.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-use-measure": {
|
||||||
|
"version": "2.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
|
||||||
|
"integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.13",
|
||||||
|
"react-dom": ">=16.13"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
@ -15391,6 +15646,15 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "3.25.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.7.tgz",
|
||||||
|
"integrity": "sha512-YGdT1cVRmKkOg6Sq7vY7IkxdphySKnXhaUmFI4r4FcuFVNgpCb9tZfNwXbT6BPjD5oz0nubFsoo9pIqKrDcCvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/zustand": {
|
"node_modules/zustand": {
|
||||||
"version": "5.0.4",
|
"version": "5.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.4.tgz",
|
||||||
|
|||||||
11
package.json
11
package.json
@ -19,6 +19,7 @@
|
|||||||
"start": "cross-env NODE_OPTIONS=--no-deprecation next start"
|
"start": "cross-env NODE_OPTIONS=--no-deprecation next start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hookform/resolvers": "^5.0.1",
|
||||||
"@payloadcms/admin-bar": "3.38.0",
|
"@payloadcms/admin-bar": "3.38.0",
|
||||||
"@payloadcms/db-postgres": "3.38.0",
|
"@payloadcms/db-postgres": "3.38.0",
|
||||||
"@payloadcms/live-preview-react": "3.38.0",
|
"@payloadcms/live-preview-react": "3.38.0",
|
||||||
@ -37,20 +38,22 @@
|
|||||||
"@radix-ui/react-collapsible": "^1.1.10",
|
"@radix-ui/react-collapsible": "^1.1.10",
|
||||||
"@radix-ui/react-dialog": "^1.1.13",
|
"@radix-ui/react-dialog": "^1.1.13",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.14",
|
"@radix-ui/react-dropdown-menu": "^2.1.14",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-popover": "^1.1.13",
|
"@radix-ui/react-popover": "^1.1.13",
|
||||||
"@radix-ui/react-select": "^2.0.0",
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
"@radix-ui/react-separator": "^1.1.6",
|
"@radix-ui/react-separator": "^1.1.6",
|
||||||
"@radix-ui/react-slot": "^1.2.2",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@radix-ui/react-tooltip": "^1.2.6",
|
"@radix-ui/react-tooltip": "^1.2.6",
|
||||||
"@tabler/icons-react": "^3.33.0",
|
"@tabler/icons-react": "^3.33.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
"framer-motion": "^12.12.1",
|
||||||
"geist": "^1.3.0",
|
"geist": "^1.3.0",
|
||||||
"graphql": "^16.8.2",
|
"graphql": "^16.8.2",
|
||||||
"lucide-react": "^0.378.0",
|
"lucide-react": "^0.378.0",
|
||||||
|
"motion": "^12.12.1",
|
||||||
"next": "15.3.0",
|
"next": "15.3.0",
|
||||||
"next-sitemap": "^4.2.3",
|
"next-sitemap": "^4.2.3",
|
||||||
"payload": "3.38.0",
|
"payload": "3.38.0",
|
||||||
@ -58,10 +61,12 @@
|
|||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-hook-form": "7.45.4",
|
"react-hook-form": "^7.45.4",
|
||||||
|
"react-use-measure": "^2.1.7",
|
||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.3.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"zod": "^3.25.7",
|
||||||
"zustand": "^5.0.4"
|
"zustand": "^5.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { Exercise, ExerciseType, Workout } from '@/payload-types'
|
import { Exercise, ExerciseType, Workout, WorkoutType } from '@/payload-types'
|
||||||
import useWorkouts from '@/stores/Workouts'
|
import useWorkouts from '@/stores/Workouts'
|
||||||
import { PaginatedDocs } from 'payload'
|
import { PaginatedDocs } from 'payload'
|
||||||
import { ReactNode, use, useEffect } from 'react'
|
import { ReactNode, use, useEffect } from 'react'
|
||||||
@ -8,6 +8,7 @@ import { ReactNode, use, useEffect } from 'react'
|
|||||||
type Props = {
|
type Props = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
getTenantWorkoutsPromise: Promise<PaginatedDocs<Workout>>
|
getTenantWorkoutsPromise: Promise<PaginatedDocs<Workout>>
|
||||||
|
getTenantWorkoutTypesPromise: Promise<PaginatedDocs<WorkoutType>>
|
||||||
getTenantExercisesPromise: Promise<PaginatedDocs<Exercise>>
|
getTenantExercisesPromise: Promise<PaginatedDocs<Exercise>>
|
||||||
getTenantExerciseTypesPromise: Promise<PaginatedDocs<ExerciseType>>
|
getTenantExerciseTypesPromise: Promise<PaginatedDocs<ExerciseType>>
|
||||||
}
|
}
|
||||||
@ -17,13 +18,15 @@ const WorkoutsLayoutSuspendedFrontend = (props: Props) => {
|
|||||||
getTenantExerciseTypesPromise,
|
getTenantExerciseTypesPromise,
|
||||||
getTenantExercisesPromise,
|
getTenantExercisesPromise,
|
||||||
getTenantWorkoutsPromise,
|
getTenantWorkoutsPromise,
|
||||||
|
getTenantWorkoutTypesPromise,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const exerciseTypeResponse = use(getTenantExerciseTypesPromise)
|
const exerciseTypeResponse = use(getTenantExerciseTypesPromise)
|
||||||
const exerciseResponse = use(getTenantExercisesPromise)
|
const exerciseResponse = use(getTenantExercisesPromise)
|
||||||
const workoutResponse = use(getTenantWorkoutsPromise)
|
const workoutResponse = use(getTenantWorkoutsPromise)
|
||||||
|
const workoutTypeResponse = use(getTenantWorkoutTypesPromise)
|
||||||
|
|
||||||
const { setExerciseTypes, setExercises, setWorkouts } = useWorkouts()
|
const { setExerciseTypes, setExercises, setWorkouts, setWorkoutTypes } = useWorkouts()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (exerciseTypeResponse?.docs?.length) setExerciseTypes(exerciseTypeResponse)
|
if (exerciseTypeResponse?.docs?.length) setExerciseTypes(exerciseTypeResponse)
|
||||||
@ -33,6 +36,10 @@ const WorkoutsLayoutSuspendedFrontend = (props: Props) => {
|
|||||||
if (exerciseResponse?.docs?.length) setExercises(exerciseResponse)
|
if (exerciseResponse?.docs?.length) setExercises(exerciseResponse)
|
||||||
}, [exerciseResponse])
|
}, [exerciseResponse])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (workoutTypeResponse?.docs?.length) setWorkoutTypes(workoutTypeResponse)
|
||||||
|
}, [workoutResponse])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workoutResponse?.docs?.length) setWorkouts(workoutResponse)
|
if (workoutResponse?.docs?.length) setWorkouts(workoutResponse)
|
||||||
}, [workoutResponse])
|
}, [workoutResponse])
|
||||||
|
|||||||
@ -2,7 +2,9 @@ import { ReactNode } from 'react'
|
|||||||
import configPromise from '@payload-config'
|
import configPromise from '@payload-config'
|
||||||
import { getPayload, PaginatedDocs } from 'payload'
|
import { getPayload, PaginatedDocs } from 'payload'
|
||||||
import WorkoutsLayoutSuspendedFrontend from './layout.client'
|
import WorkoutsLayoutSuspendedFrontend from './layout.client'
|
||||||
import { Exercise, ExerciseType } from '@/payload-types'
|
import { Exercise, ExerciseType, Workout, WorkoutType } from '@/payload-types'
|
||||||
|
|
||||||
|
const TYPE_LIMIT = 50
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
params: Promise<{
|
params: Promise<{
|
||||||
@ -18,7 +20,7 @@ const WorkoutsLayout = async (props: Props) => {
|
|||||||
|
|
||||||
const getExerciseTypesPromise = payload.find({
|
const getExerciseTypesPromise = payload.find({
|
||||||
collection: 'exerciseTypes',
|
collection: 'exerciseTypes',
|
||||||
limit: 50,
|
limit: TYPE_LIMIT,
|
||||||
depth: 0,
|
depth: 0,
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@ -51,6 +53,22 @@ const WorkoutsLayout = async (props: Props) => {
|
|||||||
},
|
},
|
||||||
}) as Promise<PaginatedDocs<Exercise>>
|
}) as Promise<PaginatedDocs<Exercise>>
|
||||||
|
|
||||||
|
const getWorkoutTypesPromise = payload.find({
|
||||||
|
collection: 'workoutTypes',
|
||||||
|
limit: TYPE_LIMIT,
|
||||||
|
depth: 0,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
description: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
'tenant.slug': {
|
||||||
|
equals: tenantSlug,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}) as Promise<PaginatedDocs<WorkoutType>>
|
||||||
|
|
||||||
const getWorkoutsPromise = payload.find({
|
const getWorkoutsPromise = payload.find({
|
||||||
collection: 'workouts',
|
collection: 'workouts',
|
||||||
limit: 20,
|
limit: 20,
|
||||||
@ -68,12 +86,13 @@ const WorkoutsLayout = async (props: Props) => {
|
|||||||
equals: tenantSlug,
|
equals: tenantSlug,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}) as Promise<PaginatedDocs<ExerciseType>>
|
}) as Promise<PaginatedDocs<Workout>>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkoutsLayoutSuspendedFrontend
|
<WorkoutsLayoutSuspendedFrontend
|
||||||
getTenantExerciseTypesPromise={getExerciseTypesPromise}
|
getTenantExerciseTypesPromise={getExerciseTypesPromise}
|
||||||
getTenantExercisesPromise={getTenantExercisesPromise}
|
getTenantExercisesPromise={getTenantExercisesPromise}
|
||||||
|
getTenantWorkoutTypesPromise={getWorkoutTypesPromise}
|
||||||
getTenantWorkoutsPromise={getWorkoutsPromise}
|
getTenantWorkoutsPromise={getWorkoutsPromise}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -1,13 +1,19 @@
|
|||||||
import { DashboardContent, DashboardContentSection } from '@/components/Dashboard'
|
import { DashboardContent, DashboardContentSection } from '@/components/Dashboard'
|
||||||
|
import WorkoutPagiation from '@/components/Workouts/WorkoutPagiation'
|
||||||
|
import WorkoutListView from '@/components/Workouts/WorkoutsListView'
|
||||||
// import WorkoutsPageClient from './page.client'
|
// import WorkoutsPageClient from './page.client'
|
||||||
|
|
||||||
const WorkoutsPage = () => {
|
const WorkoutsPage = () => {
|
||||||
return (
|
return (
|
||||||
<DashboardContent className="grid grid-cols-1 xl:grid-cols-2">
|
<DashboardContent className="grid grid-cols-1 xl:grid-cols-2">
|
||||||
<DashboardContentSection className="col-span-1 xl:max-w-lg md:ml-0">
|
<DashboardContentSection
|
||||||
<h1>Workouts</h1>
|
className="col-span-1 xl:max-w-lg md:ml-0 md:max-h-[620px] md:overflow-auto"
|
||||||
|
heading={<h2 className="pb-2 pt-4 mx-4">Workouts</h2>}
|
||||||
|
footing={<WorkoutPagiation />}
|
||||||
|
>
|
||||||
|
<WorkoutListView />
|
||||||
</DashboardContentSection>
|
</DashboardContentSection>
|
||||||
<DashboardContentSection className="col-span-1 xl:max-w-lg md:mr-0">
|
<DashboardContentSection className="col-span-1 xl:max-w-lg md:mr-0 md:max-h-[620px] md:overflow-auto">
|
||||||
<h1>Exercises</h1>
|
<h1>Exercises</h1>
|
||||||
</DashboardContentSection>
|
</DashboardContentSection>
|
||||||
<DashboardContentSection className="col-span-1 xl:col-span-2">
|
<DashboardContentSection className="col-span-1 xl:col-span-2">
|
||||||
|
|||||||
@ -34,7 +34,7 @@
|
|||||||
--muted: 210 40% 96.1%;
|
--muted: 210 40% 96.1%;
|
||||||
--muted-foreground: 215.4 16.3% 46.9%;
|
--muted-foreground: 215.4 16.3% 46.9%;
|
||||||
|
|
||||||
--accent: 210 40% 96.1%;
|
--accent: 32.1 94.6% 43.7%;
|
||||||
--accent-foreground: 222.2 47.4% 11.2%;
|
--accent-foreground: 222.2 47.4% 11.2%;
|
||||||
|
|
||||||
--destructive: 0 84.2% 60.2%;
|
--destructive: 0 84.2% 60.2%;
|
||||||
@ -44,7 +44,7 @@
|
|||||||
--input: 214.3 31.8% 91.4%;
|
--input: 214.3 31.8% 91.4%;
|
||||||
--ring: 222.2 84% 4.9%;
|
--ring: 222.2 84% 4.9%;
|
||||||
|
|
||||||
--radius: 0.2rem;
|
--radius: 0.3rem;
|
||||||
|
|
||||||
--success: 196 52% 74%;
|
--success: 196 52% 74%;
|
||||||
--warning: 34 89% 85%;
|
--warning: 34 89% 85%;
|
||||||
@ -53,7 +53,7 @@
|
|||||||
--sidebar-foreground: 240 5.3% 26.1%;
|
--sidebar-foreground: 240 5.3% 26.1%;
|
||||||
--sidebar-primary: 240 5.9% 10%;
|
--sidebar-primary: 240 5.9% 10%;
|
||||||
--sidebar-primary-foreground: 0 0% 98%;
|
--sidebar-primary-foreground: 0 0% 98%;
|
||||||
--sidebar-accent: 240 4.8% 95.9%;
|
--sidebar-accent: 32.1 94.6% 43.7%;
|
||||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||||
--sidebar-border: 220 13% 91%;
|
--sidebar-border: 220 13% 91%;
|
||||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||||
@ -69,8 +69,8 @@
|
|||||||
--popover: 222.2 84% 4.9%;
|
--popover: 222.2 84% 4.9%;
|
||||||
--popover-foreground: 210 40% 98%;
|
--popover-foreground: 210 40% 98%;
|
||||||
|
|
||||||
--primary: 210 40% 98%;
|
--primary: 48 96.6% 76.7%;
|
||||||
--primary-foreground: 222.2 47.4% 11.2%;
|
--primary-foreground: 240 4.8% 15.9%;
|
||||||
|
|
||||||
--secondary: 217.2 32.6% 17.5%;
|
--secondary: 217.2 32.6% 17.5%;
|
||||||
--secondary-foreground: 210 40% 98%;
|
--secondary-foreground: 210 40% 98%;
|
||||||
@ -78,8 +78,8 @@
|
|||||||
--muted: 217.2 32.6% 17.5%;
|
--muted: 217.2 32.6% 17.5%;
|
||||||
--muted-foreground: 215 20.2% 65.1%;
|
--muted-foreground: 215 20.2% 65.1%;
|
||||||
|
|
||||||
--accent: 217.2 32.6% 17.5%;
|
--accent: 48 96.6% 76.7%;
|
||||||
--accent-foreground: 210 40% 98%;
|
--accent-foreground: 240 4.8% 15.9%;
|
||||||
|
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 210 40% 98%;
|
--destructive-foreground: 210 40% 98%;
|
||||||
@ -95,10 +95,10 @@
|
|||||||
|
|
||||||
--sidebar-background: 240 5.9% 10%;
|
--sidebar-background: 240 5.9% 10%;
|
||||||
--sidebar-foreground: 240 4.8% 95.9%;
|
--sidebar-foreground: 240 4.8% 95.9%;
|
||||||
--sidebar-primary: 224.3 76.3% 48%;
|
--sidebar-primary: 48 96.6% 76.7%;
|
||||||
--sidebar-primary-foreground: 0 0% 100%;
|
--sidebar-primary-foreground: 240 4.8% 15.9%;
|
||||||
--sidebar-accent: 240 3.7% 15.9%;
|
--sidebar-accent: 48 96.6% 76.7%;
|
||||||
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
--sidebar-accent-foreground: 240 4.8% 15.9%;
|
||||||
--sidebar-border: 240 3.7% 15.9%;
|
--sidebar-border: 240 3.7% 15.9%;
|
||||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||||
}
|
}
|
||||||
@ -122,8 +122,8 @@
|
|||||||
--popover: 222.2 84% 4.9%;
|
--popover: 222.2 84% 4.9%;
|
||||||
--popover-foreground: 210 40% 98%;
|
--popover-foreground: 210 40% 98%;
|
||||||
|
|
||||||
--primary: 210 40% 98%;
|
--primary: 48 96.6% 76.7%;
|
||||||
--primary-foreground: 222.2 47.4% 11.2%;
|
--primary-foreground: 240 4.8% 15.9%;
|
||||||
|
|
||||||
--secondary: 217.2 32.6% 17.5%;
|
--secondary: 217.2 32.6% 17.5%;
|
||||||
--secondary-foreground: 210 40% 98%;
|
--secondary-foreground: 210 40% 98%;
|
||||||
|
|||||||
@ -76,6 +76,29 @@ export const Workouts: CollectionConfig = {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
unique: true,
|
unique: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'row',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'avatar',
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: 'media',
|
||||||
|
hasMany: false,
|
||||||
|
admin: {
|
||||||
|
width: '50%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'displayImage',
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: 'media',
|
||||||
|
hasMany: false,
|
||||||
|
admin: {
|
||||||
|
width: '50%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'type',
|
name: 'type',
|
||||||
type: 'relationship',
|
type: 'relationship',
|
||||||
|
|||||||
@ -5,13 +5,27 @@ import { ReactNode } from 'react'
|
|||||||
type Props = {
|
type Props = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
className?: ClassValue
|
className?: ClassValue
|
||||||
|
heading?: ReactNode
|
||||||
|
footing?: ReactNode
|
||||||
}
|
}
|
||||||
const DashboardContentSection = (props: Props) => {
|
const DashboardContentSection = (props: Props) => {
|
||||||
const { children, className } = props
|
const { children, className, heading, footing } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={cn('mx-auto w-full p-6 rounded-xl bg-muted/50', className || '')}>
|
<section className={cn('relative mx-auto w-full rounded-xl bg-muted/50', className || '')}>
|
||||||
{children}
|
{!!heading && (
|
||||||
|
<div className="sticky top-0 left-0 w-full bg-muted font-semibold text-xl z-10">
|
||||||
|
{heading}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="p-2">{children}</div>
|
||||||
|
|
||||||
|
{!!footing && (
|
||||||
|
<div className="sticky bottom-0 left-0 w-full bg-muted rounded-b-lg font-semibold text-xl z-10">
|
||||||
|
{footing}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/components/Workouts/WorkoutPagiation.tsx
Normal file
36
src/components/Workouts/WorkoutPagiation.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import useWorkouts from '@/stores/Workouts'
|
||||||
|
import { Button } from '../ui/button'
|
||||||
|
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
|
||||||
|
|
||||||
|
const WorkoutPagiation = () => {
|
||||||
|
const { workouts } = useWorkouts()
|
||||||
|
return (
|
||||||
|
<nav
|
||||||
|
aria-label="Workout Pagination"
|
||||||
|
className="flex items-center justify-between px-4 py-3 sm:px-6"
|
||||||
|
>
|
||||||
|
<div className="hidden sm:block">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Showing <span className="font-medium">{workouts?.pagingCounter}</span> to{' '}
|
||||||
|
<span className="font-medium">
|
||||||
|
{(workouts?.pagingCounter || 0) + ((workouts?.limit || 0) - 1)}
|
||||||
|
</span>{' '}
|
||||||
|
of <span className="font-medium">{workouts?.totalDocs}</span> results
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-1 justify-between gap-1 sm:justify-end">
|
||||||
|
<Button variant="ghost" disabled={!workouts?.hasPrevPage}>
|
||||||
|
<ChevronLeftIcon aria-hidden="true" className="size-5 flex-none text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button variant="ghost" disabled={!workouts?.hasNextPage}>
|
||||||
|
<ChevronRightIcon aria-hidden="true" className="size-5 flex-none text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WorkoutPagiation
|
||||||
84
src/components/Workouts/WorkoutPopoutEditForm.tsx
Normal file
84
src/components/Workouts/WorkoutPopoutEditForm.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
'use client'
|
||||||
|
import { Workout } 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 { useForm } from 'react-hook-form'
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
|
import { Input } from '../ui/input'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
const editFormSchema = z.object({
|
||||||
|
name: z.string().min(2, {
|
||||||
|
message: 'Workout name must be at least 2 characters.',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
workout: Workout
|
||||||
|
}
|
||||||
|
const WorkoutPopoutEditForm = (props: Props) => {
|
||||||
|
const w = props.workout
|
||||||
|
const form = useForm<z.infer<typeof editFormSchema>>({
|
||||||
|
resolver: zodResolver(editFormSchema),
|
||||||
|
defaultValues: {
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleEditSubmit = (e: any) => {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PopoverRoot>
|
||||||
|
<PopoverTrigger className="w-full">
|
||||||
|
<div className="w-full flex items-center justify-between my-2">
|
||||||
|
<span className="inline-block text-lg font-medium">{w.name}</span>
|
||||||
|
<span className="inline-block text-xs py-0.5 px-1 border border-primary rounded-lg">
|
||||||
|
Edit
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="rounded-lg">
|
||||||
|
<PopoverHeader>Edit Workout</PopoverHeader>
|
||||||
|
<PopoverBody>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(handleEditSubmit)} className="space-y-8">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Workout Name</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="shadcn" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>This is your public display name.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</PopoverBody>
|
||||||
|
</PopoverContent>
|
||||||
|
</PopoverRoot>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WorkoutPopoutEditForm
|
||||||
94
src/components/Workouts/WorkoutsListView.tsx
Normal file
94
src/components/Workouts/WorkoutsListView.tsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
'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()
|
||||||
|
|
||||||
|
const testWorkouts = {
|
||||||
|
docs: [
|
||||||
|
...(workouts?.docs || []),
|
||||||
|
...(workouts?.docs || []),
|
||||||
|
...(workouts?.docs || []),
|
||||||
|
...(workouts?.docs || []),
|
||||||
|
...(workouts?.docs || []),
|
||||||
|
...(workouts?.docs || []),
|
||||||
|
...(workouts?.docs || []),
|
||||||
|
...(workouts?.docs || []),
|
||||||
|
...(workouts?.docs || []),
|
||||||
|
...(workouts?.docs || []),
|
||||||
|
...(workouts?.docs || []),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const getWorkoutTypeById = useCallback(
|
||||||
|
(typeId: number) => {
|
||||||
|
const foundWorkoutType = workoutTypes?.docs.find((t) => t.id === typeId)
|
||||||
|
return foundWorkoutType
|
||||||
|
},
|
||||||
|
[workoutTypes, workouts],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul role="list" className="divide-y divide-sidebar-border">
|
||||||
|
{testWorkouts?.docs.map((w, i) => {
|
||||||
|
const avatar = (w.avatar as Media) || undefined
|
||||||
|
return (
|
||||||
|
<li key={(w.name || '') + i} className="px-4 py-4 sm:px-0 flex justify-between">
|
||||||
|
<div className="flex min-w-0 gap-x-4 ">
|
||||||
|
<Avatar className="flex-none size-12 rounded-full">
|
||||||
|
<AvatarImage src={avatar?.url || ''} alt={w.name || ''} />
|
||||||
|
<AvatarFallback className="rounded-full">
|
||||||
|
{makeAcronym(w.name || '')}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="min-w-0 flex-auto">
|
||||||
|
<p className="text-sm/6 font-semibold ">
|
||||||
|
<Link href={''}>{w.name}</Link>
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 flex text-xs/5 text-muted-foreground">{w.description} </p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex shrink-0 items-center gap-x-4">
|
||||||
|
<div className="hidden sm:flex sm:flex-col sm:items-end">
|
||||||
|
<p className="text-sm/6">
|
||||||
|
{w.type
|
||||||
|
?.map((t) => {
|
||||||
|
console.log(t)
|
||||||
|
if (typeof t === 'number') return getWorkoutTypeById(t)?.name
|
||||||
|
else return t.name
|
||||||
|
})
|
||||||
|
.filter((t) => !!t)
|
||||||
|
.join(', ')}
|
||||||
|
</p>
|
||||||
|
{!!w.durationMinutes && (
|
||||||
|
<p className="mt-1 text-xs/5 text-gray-500">
|
||||||
|
<span>{w.durationMinutes} mins</span>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
className="block rounded-lg"
|
||||||
|
onClick={() => console.log('clicked: ', w.name)}
|
||||||
|
>
|
||||||
|
<Edit aria-hidden="true" className="size-5 flex-none text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WorkoutListView
|
||||||
@ -2,7 +2,7 @@ import * as React from "react"
|
|||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils/index"
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||||
|
|||||||
178
src/components/ui/form.tsx
Normal file
178
src/components/ui/form.tsx
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
FormProvider,
|
||||||
|
useFormContext,
|
||||||
|
type ControllerProps,
|
||||||
|
type FieldPath,
|
||||||
|
type FieldValues,
|
||||||
|
} from "react-hook-form"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils/index"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
|
||||||
|
const Form = FormProvider
|
||||||
|
|
||||||
|
type FormFieldContextValue<
|
||||||
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
|
> = {
|
||||||
|
name: TName
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||||
|
{} as FormFieldContextValue
|
||||||
|
)
|
||||||
|
|
||||||
|
const FormField = <
|
||||||
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
|
>({
|
||||||
|
...props
|
||||||
|
}: ControllerProps<TFieldValues, TName>) => {
|
||||||
|
return (
|
||||||
|
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||||
|
<Controller {...props} />
|
||||||
|
</FormFieldContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const useFormField = () => {
|
||||||
|
const fieldContext = React.useContext(FormFieldContext)
|
||||||
|
const itemContext = React.useContext(FormItemContext)
|
||||||
|
const { getFieldState, formState } = useFormContext()
|
||||||
|
|
||||||
|
const fieldState = getFieldState(fieldContext.name, formState)
|
||||||
|
|
||||||
|
if (!fieldContext) {
|
||||||
|
throw new Error("useFormField should be used within <FormField>")
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = itemContext
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: fieldContext.name,
|
||||||
|
formItemId: `${id}-form-item`,
|
||||||
|
formDescriptionId: `${id}-form-item-description`,
|
||||||
|
formMessageId: `${id}-form-item-message`,
|
||||||
|
...fieldState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FormItemContextValue = {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||||
|
{} as FormItemContextValue
|
||||||
|
)
|
||||||
|
|
||||||
|
const FormItem = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const id = React.useId()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItemContext.Provider value={{ id }}>
|
||||||
|
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||||
|
</FormItemContext.Provider>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormItem.displayName = "FormItem"
|
||||||
|
|
||||||
|
const FormLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { error, formItemId } = useFormField()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn(error && "text-destructive", className)}
|
||||||
|
htmlFor={formItemId}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormLabel.displayName = "FormLabel"
|
||||||
|
|
||||||
|
const FormControl = React.forwardRef<
|
||||||
|
React.ElementRef<typeof Slot>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof Slot>
|
||||||
|
>(({ ...props }, ref) => {
|
||||||
|
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Slot
|
||||||
|
ref={ref}
|
||||||
|
id={formItemId}
|
||||||
|
aria-describedby={
|
||||||
|
!error
|
||||||
|
? `${formDescriptionId}`
|
||||||
|
: `${formDescriptionId} ${formMessageId}`
|
||||||
|
}
|
||||||
|
aria-invalid={!!error}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormControl.displayName = "FormControl"
|
||||||
|
|
||||||
|
const FormDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { formDescriptionId } = useFormField()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
id={formDescriptionId}
|
||||||
|
className={cn("text-[0.8rem] text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormDescription.displayName = "FormDescription"
|
||||||
|
|
||||||
|
const FormMessage = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, children, ...props }, ref) => {
|
||||||
|
const { error, formMessageId } = useFormField()
|
||||||
|
const body = error ? String(error?.message ?? "") : children
|
||||||
|
|
||||||
|
if (!body) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
id={formMessageId}
|
||||||
|
className={cn("text-[0.8rem] font-medium text-destructive", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormMessage.displayName = "FormMessage"
|
||||||
|
|
||||||
|
export {
|
||||||
|
useFormField,
|
||||||
|
Form,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormMessage,
|
||||||
|
FormField,
|
||||||
|
}
|
||||||
@ -1,19 +1,26 @@
|
|||||||
'use client'
|
"use client"
|
||||||
|
|
||||||
import { cn } from '@/utilities/ui'
|
import * as React from "react"
|
||||||
import * as LabelPrimitive from '@radix-ui/react-label'
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
import { type VariantProps, cva } from 'class-variance-authority'
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import * as React from 'react'
|
|
||||||
|
import { cn } from "@/lib/utils/index"
|
||||||
|
|
||||||
const labelVariants = cva(
|
const labelVariants = cva(
|
||||||
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Label: React.FC<
|
const Label = React.forwardRef<
|
||||||
{ ref?: React.Ref<HTMLLabelElement> } & React.ComponentProps<typeof LabelPrimitive.Root> &
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
VariantProps<typeof labelVariants>
|
VariantProps<typeof labelVariants>
|
||||||
> = ({ className, ref, ...props }) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<LabelPrimitive.Root className={cn(labelVariants(), className)} ref={ref} {...props} />
|
<LabelPrimitive.Root
|
||||||
)
|
ref={ref}
|
||||||
|
className={cn(labelVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Label.displayName = LabelPrimitive.Root.displayName
|
||||||
|
|
||||||
export { Label }
|
export { Label }
|
||||||
|
|||||||
120
src/components/ui/neumorph-button.tsx
Normal file
120
src/components/ui/neumorph-button.tsx
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import type React from 'react'
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
import { motion, type HTMLMotionProps } from 'framer-motion'
|
||||||
|
import { Loader2 } from 'lucide-react'
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
// Base styles
|
||||||
|
'justify-center px-4 text-sm font-medium items-center transition-[box-shadow,background-color] disabled:cursor-not-allowed disabled:opacity-50 flex active:transition-none',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
intent: {
|
||||||
|
default: [
|
||||||
|
'bg-[#36322F]',
|
||||||
|
'text-[#fff]',
|
||||||
|
'hover:enabled:bg-[#4a4542]',
|
||||||
|
'disabled:bg-[#8c8885]',
|
||||||
|
'[box-shadow:inset_0px_-2.108433723449707px_0px_0px_#171310,_0px_1.2048193216323853px_6.325301647186279px_0px_rgba(58,_33,_8,_58%)]',
|
||||||
|
'hover:enabled:[box-shadow:inset_0px_-2.53012px_0px_0px_#171310,_0px_1.44578px_7.59036px_0px_rgba(58,_33,_8,_64%)]',
|
||||||
|
'disabled:shadow-none',
|
||||||
|
'active:bg-[#2A2724]',
|
||||||
|
'active:[box-shadow:inset_0px_-1.5px_0px_0px_#171310,_0px_0.5px_2px_0px_rgba(58,_33,_8,_70%)]',
|
||||||
|
],
|
||||||
|
primary: [
|
||||||
|
'bg-primary',
|
||||||
|
'text-primary-foreground',
|
||||||
|
'hover:enabled:bg-accent',
|
||||||
|
'disabled:bg-[#9FC3F5]',
|
||||||
|
'[box-shadow:inset_0px_-2.108433723449707px_0px_0px_hsla(var(--accent)),_0px_1.2048193216323853px_6.325301647186279px_0px_hsla(var(--accent))]',
|
||||||
|
'hover:enabled:[box-shadow:inset_0px_-2.53012px_0px_0px_hsla(var(--accent)),_0px_1.44578px_7.59036px_0px_hsla(var(--accent))]',
|
||||||
|
'disabled:shadow-none',
|
||||||
|
'active:bg-[#1A68D1]',
|
||||||
|
'active:[box-shadow:inset_0px_-1.5px_0px_0px_#1554AB,_0px_0.5px_2px_0px_rgba(28,_100,_242,_70%)]',
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'bg-[#FFFFFF]',
|
||||||
|
'text-[#36322F]',
|
||||||
|
'hover:enabled:bg-[#F8F8F8]',
|
||||||
|
'disabled:bg-[#F0F0F0]',
|
||||||
|
'[box-shadow:inset_0px_-2.108433723449707px_0px_0px_#E0E0E0,_0px_1.2048193216323853px_6.325301647186279px_0px_rgba(0,_0,_0,_10%)]',
|
||||||
|
'hover:enabled:[box-shadow:inset_0px_-2.53012px_0px_0px_#E8E8E8,_0px_1.44578px_7.59036px_0px_rgba(0,_0,_0,_12%)]',
|
||||||
|
'disabled:shadow-none',
|
||||||
|
'border',
|
||||||
|
'border-[#E0E0E0]',
|
||||||
|
'active:bg-[#F0F0F0]',
|
||||||
|
'active:[box-shadow:inset_0px_-1.5px_0px_0px_#D8D8D8,_0px_0.5px_2px_0px_rgba(0,_0,_0,_15%)]',
|
||||||
|
],
|
||||||
|
danger: [
|
||||||
|
'bg-[#E6492D]',
|
||||||
|
'text-[#fff]',
|
||||||
|
'hover:enabled:bg-[#F05B41]',
|
||||||
|
'disabled:bg-[#F5A799]',
|
||||||
|
'[box-shadow:inset_0px_-2.108433723449707px_0px_0px_#D63A1F,_0px_1.2048193216323853px_6.325301647186279px_0px_rgba(214,_58,_31,_58%)]',
|
||||||
|
'hover:enabled:[box-shadow:inset_0px_-2.53012px_0px_0px_#E6492D,_0px_1.44578px_7.59036px_0px_rgba(214,_58,_31,_64%)]',
|
||||||
|
'disabled:shadow-none',
|
||||||
|
'active:bg-[#D63A1F]',
|
||||||
|
'active:[box-shadow:inset_0px_-1.5px_0px_0px_#B22E17,_0px_0.5px_2px_0px_rgba(214,_58,_31,_70%)]',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
small: ['text-xs', 'py-1', 'px-2', 'h-9', 'rounded-[8px]'],
|
||||||
|
medium: ['text-base', 'py-2', 'px-4', 'h-11', 'rounded-[9px]'],
|
||||||
|
large: ['text-lg', 'py-3', 'px-6', 'h-14', 'rounded-[11px]'],
|
||||||
|
},
|
||||||
|
fullWidth: {
|
||||||
|
true: 'w-full',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
compoundVariants: [
|
||||||
|
{
|
||||||
|
intent: ['default', 'primary', 'secondary', 'danger'],
|
||||||
|
size: 'medium',
|
||||||
|
className: 'uppercase',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultVariants: {
|
||||||
|
intent: 'default',
|
||||||
|
size: 'medium',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface NeumorphButtonProps
|
||||||
|
extends HTMLMotionProps<'button'>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
children: React.ReactNode
|
||||||
|
loading?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const NeumorphButton: React.FC<NeumorphButtonProps> = ({
|
||||||
|
className,
|
||||||
|
intent,
|
||||||
|
size,
|
||||||
|
fullWidth,
|
||||||
|
children,
|
||||||
|
loading = false,
|
||||||
|
disabled,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<motion.button
|
||||||
|
className={buttonVariants({ intent, size, fullWidth, className })}
|
||||||
|
disabled={disabled || loading}
|
||||||
|
whileTap={{ scale: 0.98 }}
|
||||||
|
whileHover={{ scale: 1.02 }}
|
||||||
|
transition={{ type: 'spring', stiffness: 400, damping: 10 }}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{loading ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : null}
|
||||||
|
<motion.span
|
||||||
|
initial={{ opacity: 1 }}
|
||||||
|
animate={{ opacity: loading ? 0.7 : 1 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.span>
|
||||||
|
</motion.button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NeumorphButton
|
||||||
@ -1,33 +1,254 @@
|
|||||||
"use client"
|
'use client'
|
||||||
|
|
||||||
import * as React from "react"
|
import React, {
|
||||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
createContext,
|
||||||
|
HTMLAttributes,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useId,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
import { X } from 'lucide-react'
|
||||||
|
import { AnimatePresence, MotionConfig, motion } from 'motion/react'
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils/index'
|
||||||
|
|
||||||
const Popover = PopoverPrimitive.Root
|
const TRANSITION = {
|
||||||
|
type: 'spring',
|
||||||
|
bounce: 0.05,
|
||||||
|
duration: 0.3,
|
||||||
|
}
|
||||||
|
|
||||||
const PopoverTrigger = PopoverPrimitive.Trigger
|
function useClickOutside(ref: React.RefObject<HTMLElement>, handler: () => void) {
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||||
|
handler()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const PopoverAnchor = PopoverPrimitive.Anchor
|
document.addEventListener('mousedown', handleClickOutside)
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside)
|
||||||
|
}
|
||||||
|
}, [ref, handler])
|
||||||
|
}
|
||||||
|
|
||||||
const PopoverContent = React.forwardRef<
|
interface PopoverContextType {
|
||||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
isOpen: boolean
|
||||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
openPopover: () => void
|
||||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
closePopover: () => void
|
||||||
<PopoverPrimitive.Portal>
|
uniqueId: string
|
||||||
<PopoverPrimitive.Content
|
note: string
|
||||||
ref={ref}
|
setNote: (note: string) => void
|
||||||
align={align}
|
}
|
||||||
sideOffset={sideOffset}
|
|
||||||
|
const PopoverContext = createContext<PopoverContextType | undefined>(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 (
|
||||||
|
<PopoverContext.Provider value={popoverLogic}>
|
||||||
|
<MotionConfig transition={TRANSITION}>
|
||||||
|
<div className={cn('relative flex items-center justify-center isolate', className)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</MotionConfig>
|
||||||
|
</PopoverContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PopoverTriggerProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PopoverTrigger({ children, className }: PopoverTriggerProps) {
|
||||||
|
const { openPopover, uniqueId } = usePopover()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.button
|
||||||
|
key="button"
|
||||||
|
layoutId={`popover-${uniqueId}`}
|
||||||
|
className={cn(className)}
|
||||||
|
onClick={openPopover}
|
||||||
|
>
|
||||||
|
<motion.span layoutId={`popover-label-${uniqueId}`}>{children}</motion.span>
|
||||||
|
</motion.button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PopoverContentProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PopoverContent({ children, className }: PopoverContentProps) {
|
||||||
|
const { isOpen, closePopover, uniqueId } = usePopover()
|
||||||
|
const formContainerRef = useRef<HTMLDivElement>(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 (
|
||||||
|
<AnimatePresence>
|
||||||
|
{isOpen && (
|
||||||
|
<motion.div
|
||||||
|
ref={formContainerRef}
|
||||||
|
layoutId={`popover-${uniqueId}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]",
|
'absolute h-[240px] overflow-hidden border border-primary/10 bg-sidebar outline-none z-50 container rounded-lg',
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
style={{
|
||||||
/>
|
top: 'auto', // Remove any top positioning
|
||||||
</PopoverPrimitive.Portal>
|
left: 'auto', // Remove any left positioning
|
||||||
))
|
transform: 'none', // Remove any transform
|
||||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
interface PopoverFooterProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PopoverFooter({ children, className }: PopoverFooterProps) {
|
||||||
|
return (
|
||||||
|
<div key="close" className={cn('flex justify-between px-4 py-3', className)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PopoverCloseButtonProps {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PopoverCloseButton({ className }: PopoverCloseButtonProps) {
|
||||||
|
const { closePopover } = usePopover()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={cn('flex items-center', className)}
|
||||||
|
onClick={closePopover}
|
||||||
|
aria-label="Close popover"
|
||||||
|
>
|
||||||
|
<X size={16} className="text-zinc-900 dark:text-zinc-100" />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PopoverSubmitButtonProps {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PopoverSubmitButton({ className }: PopoverSubmitButtonProps) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
'relative ml-1 flex h-8 shrink-0 scale-100 select-none appearance-none items-center justify-center rounded-lg border border-zinc-950/10 bg-transparent px-2 text-sm text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-800 focus-visible:ring-2 active:scale-[0.98] dark:border-zinc-50/10 dark:text-zinc-50 dark:hover:bg-zinc-800',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
type="submit"
|
||||||
|
aria-label="Submit note"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PopoverHeader({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
className?: string
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className={cn('px-4 py-2 font-semibold text-zinc-900 dark:text-zinc-100', className)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PopoverBody({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
className?: string
|
||||||
|
}) {
|
||||||
|
return <div className={cn('p-4', className)}>{children}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
// New component: PopoverButton
|
||||||
|
export function PopoverButton({
|
||||||
|
children,
|
||||||
|
onClick,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
onClick?: () => void
|
||||||
|
className?: string
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
'flex w-full items-center gap-2 rounded-md px-4 py-2 text-left text-sm hover:bg-zinc-100 dark:hover:bg-zinc-700',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
280
src/components/ui/text-animate.tsx
Normal file
280
src/components/ui/text-animate.tsx
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { FC, useEffect, useRef } from "react"
|
||||||
|
import { HTMLMotionProps, motion, useAnimation, useInView } from "motion/react"
|
||||||
|
|
||||||
|
type AnimationType =
|
||||||
|
| "fadeIn"
|
||||||
|
| "fadeInUp"
|
||||||
|
| "popIn"
|
||||||
|
| "shiftInUp"
|
||||||
|
| "rollIn"
|
||||||
|
| "whipIn"
|
||||||
|
| "whipInUp"
|
||||||
|
| "calmInUp"
|
||||||
|
|
||||||
|
interface Props extends HTMLMotionProps<"div"> {
|
||||||
|
text: string
|
||||||
|
type?: AnimationType
|
||||||
|
delay?: number
|
||||||
|
duration?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const animationVariants = {
|
||||||
|
fadeIn: {
|
||||||
|
container: {
|
||||||
|
hidden: { opacity: 0 },
|
||||||
|
visible: (i: number = 1) => ({
|
||||||
|
opacity: 1,
|
||||||
|
transition: { staggerChildren: 0.05, delayChildren: i * 0.3 },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
child: {
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
y: [0, -10, 0],
|
||||||
|
transition: {
|
||||||
|
type: "spring",
|
||||||
|
damping: 12,
|
||||||
|
stiffness: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hidden: { opacity: 0, y: 10 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fadeInUp: {
|
||||||
|
container: {
|
||||||
|
hidden: { opacity: 0 },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: { staggerChildren: 0.1, delayChildren: 0.2 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
child: {
|
||||||
|
visible: { opacity: 1, y: 0, transition: { duration: 0.5 } },
|
||||||
|
hidden: { opacity: 0, y: 20 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
popIn: {
|
||||||
|
container: {
|
||||||
|
hidden: { scale: 0 },
|
||||||
|
visible: {
|
||||||
|
scale: 1,
|
||||||
|
transition: { staggerChildren: 0.05, delayChildren: 0.2 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
child: {
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
scale: 1.1,
|
||||||
|
transition: { type: "spring", damping: 15, stiffness: 400 },
|
||||||
|
},
|
||||||
|
hidden: { opacity: 0, scale: 0 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
calmInUp: {
|
||||||
|
container: {
|
||||||
|
hidden: {},
|
||||||
|
visible: (i: number = 1) => ({
|
||||||
|
transition: { staggerChildren: 0.01, delayChildren: 0.2 * i },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
child: {
|
||||||
|
hidden: {
|
||||||
|
y: "200%",
|
||||||
|
transition: { ease: [0.455, 0.03, 0.515, 0.955], duration: 0.85 },
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
y: 0,
|
||||||
|
transition: {
|
||||||
|
ease: [0.125, 0.92, 0.69, 0.975], // Drawing attention to dynamic content or interactive elements, where the animation needs to be engaging but not abrupt
|
||||||
|
duration: 0.75,
|
||||||
|
// ease: [0.455, 0.03, 0.515, 0.955], // smooth and gradual acceleration followed by a steady deceleration towards the end of the animation
|
||||||
|
// ease: [0.115, 0.955, 0.655, 0.939], // smooth and gradual acceleration followed by a steady deceleration towards the end of the animation
|
||||||
|
// ease: [0.09, 0.88, 0.68, 0.98], // Very Gentle Onset, Swift Mid-Section, Soft Landing
|
||||||
|
// ease: [0.11, 0.97, 0.64, 0.945], // Minimal Start, Energetic Acceleration, Smooth Closure
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shiftInUp: {
|
||||||
|
container: {
|
||||||
|
hidden: {},
|
||||||
|
visible: (i: number = 1) => ({
|
||||||
|
transition: { staggerChildren: 0.01, delayChildren: 0.2 * i },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
child: {
|
||||||
|
hidden: {
|
||||||
|
y: "100%", // Starting from below but not too far to ensure a dramatic but manageable shift.
|
||||||
|
transition: {
|
||||||
|
ease: [0.75, 0, 0.25, 1], // Starting quickly
|
||||||
|
duration: 0.6, // Shortened duration for a more dramatic start
|
||||||
|
},
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
y: 0,
|
||||||
|
transition: {
|
||||||
|
duration: 0.8, // Slightly longer to accommodate the slow middle and swift end
|
||||||
|
ease: [0.22, 1, 0.36, 1], // This easing function starts quickly (dramatic shift), slows down (slow middle), and ends quickly (clean swift end)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
whipInUp: {
|
||||||
|
container: {
|
||||||
|
hidden: {},
|
||||||
|
visible: (i: number = 1) => ({
|
||||||
|
transition: { staggerChildren: 0.01, delayChildren: 0.2 * i },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
child: {
|
||||||
|
hidden: {
|
||||||
|
y: "200%",
|
||||||
|
transition: { ease: [0.455, 0.03, 0.515, 0.955], duration: 0.45 },
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
y: 0,
|
||||||
|
transition: {
|
||||||
|
ease: [0.5, -0.15, 0.25, 1.05],
|
||||||
|
duration: 0.75,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rollIn: {
|
||||||
|
container: {
|
||||||
|
hidden: {},
|
||||||
|
visible: {},
|
||||||
|
},
|
||||||
|
child: {
|
||||||
|
hidden: {
|
||||||
|
opacity: 0,
|
||||||
|
y: `0.25em`,
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
y: `0em`,
|
||||||
|
transition: {
|
||||||
|
duration: 0.65,
|
||||||
|
ease: [0.65, 0, 0.75, 1], // Great! Swift Beginning, Prolonged Ease, Quick Finish
|
||||||
|
// ease: [0.75, 0.05, 0.85, 1], // Quick Start, Smooth Middle, Sharp End
|
||||||
|
// ease: [0.7, -0.25, 0.9, 1.25], // Fast Acceleration, Gentle Slowdown, Sudden Snap
|
||||||
|
// ease: [0.7, -0.5, 0.85, 1.5], // Quick Leap, Soft Glide, Snappy Closure
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
whipIn: {
|
||||||
|
container: {
|
||||||
|
hidden: {},
|
||||||
|
visible: {},
|
||||||
|
},
|
||||||
|
child: {
|
||||||
|
hidden: {
|
||||||
|
opacity: 0,
|
||||||
|
y: `0.35em`,
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
y: `0em`,
|
||||||
|
transition: {
|
||||||
|
duration: 0.45,
|
||||||
|
// ease: [0.75, 0.05, 0.85, 1], // Quick Start, Smooth Middle, Sharp End
|
||||||
|
// ease: [0.7, -0.25, 0.9, 1.25], // Fast Acceleration, Gentle Slowdown, Sudden Snap
|
||||||
|
// ease: [0.65, 0, 0.75, 1], // Great! Swift Beginning, Prolonged Ease, Quick Finish
|
||||||
|
ease: [0.85, 0.1, 0.9, 1.2], // Rapid Initiation, Subtle Slow, Sharp Conclusion
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextAnimate: FC<Props> = ({
|
||||||
|
text,
|
||||||
|
type = "whipInUp",
|
||||||
|
...props
|
||||||
|
}: Props) => {
|
||||||
|
// const { ref, inView } = useInView({
|
||||||
|
// threshold: 0.5,
|
||||||
|
// triggerOnce: true,
|
||||||
|
// });
|
||||||
|
|
||||||
|
const ref = useRef(null)
|
||||||
|
const isInView = useInView(ref, { once: true })
|
||||||
|
|
||||||
|
const letters = Array.from(text)
|
||||||
|
const { container, child } = animationVariants[type]
|
||||||
|
|
||||||
|
const ctrls = useAnimation()
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (isInView) {
|
||||||
|
// ctrls.start("visible");
|
||||||
|
// }
|
||||||
|
// if (!isInView) {
|
||||||
|
// ctrls.start("hidden");
|
||||||
|
// }
|
||||||
|
// }, [ctrls, isInView]);
|
||||||
|
|
||||||
|
if (type === "rollIn" || type === "whipIn") {
|
||||||
|
return (
|
||||||
|
<h2 className="mt-10 text-3xl font-black text-black dark:text-neutral-100 py-5 pb-8 px-8 md:text-5xl">
|
||||||
|
{text.split(" ").map((word, index) => {
|
||||||
|
return (
|
||||||
|
<motion.span
|
||||||
|
ref={ref}
|
||||||
|
className="inline-block mr-[0.25em] whitespace-nowrap"
|
||||||
|
aria-hidden="true"
|
||||||
|
key={index}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
variants={container}
|
||||||
|
transition={{
|
||||||
|
delayChildren: index * 0.13,
|
||||||
|
// delayChildren: index * 0.35,
|
||||||
|
staggerChildren: 0.025,
|
||||||
|
// staggerChildren: 0.05,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{word.split("").map((character, index) => {
|
||||||
|
return (
|
||||||
|
<motion.span
|
||||||
|
aria-hidden="true"
|
||||||
|
key={index}
|
||||||
|
variants={child}
|
||||||
|
className="inline-block -mr-[0.01em]"
|
||||||
|
>
|
||||||
|
{character}
|
||||||
|
</motion.span>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</motion.span>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</h2>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.h2
|
||||||
|
style={{ display: "flex", overflow: "hidden" }}
|
||||||
|
role="heading"
|
||||||
|
variants={container}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
className="mt-10 text-4xl font-black text-black dark:text-neutral-100 py-5 pb-8 px-8 md:text-5xl"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{letters.map((letter, index) => (
|
||||||
|
<motion.span key={index} variants={child}>
|
||||||
|
{letter === " " ? "\u00A0" : letter}
|
||||||
|
</motion.span>
|
||||||
|
))}
|
||||||
|
</motion.h2>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { TextAnimate }
|
||||||
|
export default TextAnimate
|
||||||
@ -892,6 +892,8 @@ export interface Workout {
|
|||||||
id: number;
|
id: number;
|
||||||
tenant?: (number | null) | Tenant;
|
tenant?: (number | null) | Tenant;
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
|
avatar?: (number | null) | Media;
|
||||||
|
displayImage?: (number | null) | Media;
|
||||||
type?: (number | WorkoutType)[] | null;
|
type?: (number | WorkoutType)[] | null;
|
||||||
difficulty?: number | null;
|
difficulty?: number | null;
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
@ -1897,6 +1899,8 @@ export interface EquipmentsSelect<T extends boolean = true> {
|
|||||||
export interface WorkoutsSelect<T extends boolean = true> {
|
export interface WorkoutsSelect<T extends boolean = true> {
|
||||||
tenant?: T;
|
tenant?: T;
|
||||||
name?: T;
|
name?: T;
|
||||||
|
avatar?: T;
|
||||||
|
displayImage?: T;
|
||||||
type?: T;
|
type?: T;
|
||||||
difficulty?: T;
|
difficulty?: T;
|
||||||
description?: T;
|
description?: T;
|
||||||
|
|||||||
@ -1,17 +1,19 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import { Exercise, ExerciseType, Workout } from '@/payload-types'
|
import { Exercise, ExerciseType, Workout, WorkoutType } from '@/payload-types'
|
||||||
import { PaginatedDocs } from 'payload'
|
import { PaginatedDocs } from 'payload'
|
||||||
|
|
||||||
export type WorkoutsProps = {
|
export type WorkoutsProps = {
|
||||||
exercises?: PaginatedDocs<Exercise>,
|
exercises?: PaginatedDocs<Exercise>,
|
||||||
exerciseTypes?: PaginatedDocs<ExerciseType>,
|
exerciseTypes?: PaginatedDocs<ExerciseType>,
|
||||||
workouts?: PaginatedDocs<Workout>,
|
workouts?: PaginatedDocs<Workout>,
|
||||||
|
workoutTypes?: PaginatedDocs<WorkoutType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WorkoutsMethods = {
|
export type WorkoutsMethods = {
|
||||||
setExercises: (exercises?: PaginatedDocs<Exercise>) => void,
|
setExercises: (exercises?: PaginatedDocs<Exercise>) => void,
|
||||||
setExerciseTypes: (exerciseTypes?: PaginatedDocs<ExerciseType>) => void,
|
setExerciseTypes: (exerciseTypes?: PaginatedDocs<ExerciseType>) => void,
|
||||||
setWorkouts: (workouts?: PaginatedDocs<Workout>) => void,
|
setWorkouts: (workouts?: PaginatedDocs<Workout>) => void,
|
||||||
|
setWorkoutTypes: (workoutTypes?: PaginatedDocs<WorkoutType>) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WorkoutsStore = WorkoutsProps & WorkoutsMethods
|
export type WorkoutsStore = WorkoutsProps & WorkoutsMethods
|
||||||
@ -23,6 +25,7 @@ const useWorkouts = create<WorkoutsStore>((set) => ({
|
|||||||
setExercises: (exercises?: PaginatedDocs<Exercise>) => set(() => ({ exercises: exercises })),
|
setExercises: (exercises?: PaginatedDocs<Exercise>) => set(() => ({ exercises: exercises })),
|
||||||
setExerciseTypes: (exerciseTypes?: PaginatedDocs<ExerciseType>) => set(() => ({ exerciseTypes: exerciseTypes })),
|
setExerciseTypes: (exerciseTypes?: PaginatedDocs<ExerciseType>) => set(() => ({ exerciseTypes: exerciseTypes })),
|
||||||
setWorkouts: (workouts?: PaginatedDocs<Workout>) => set(() => ({ workouts: workouts })),
|
setWorkouts: (workouts?: PaginatedDocs<Workout>) => set(() => ({ workouts: workouts })),
|
||||||
|
setWorkoutTypes: (workoutTypes?: PaginatedDocs<WorkoutType>) => set(() => ({ workoutTypes: workoutTypes }))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export default useWorkouts
|
export default useWorkouts
|
||||||
|
|||||||
8
src/utilities/makeAcronym.ts
Normal file
8
src/utilities/makeAcronym.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
const makeAcronym = (name: string, maxLength: number = 2) => {
|
||||||
|
return name
|
||||||
|
.split(' ')
|
||||||
|
.map((part) => part[0])
|
||||||
|
.slice(0, maxLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default makeAcronym
|
||||||
Loading…
x
Reference in New Issue
Block a user