From 6b16de4335c24f9b22b1038bfe65858f6eba4a25 Mon Sep 17 00:00:00 2001 From: bcheung Date: Tue, 24 Oct 2023 08:16:02 -0400 Subject: [PATCH 01/19] update --- .gitignore | 4 +++- README.md | 9 ++++++++- package.json | 4 ++-- yarn.lock | 36 ++++++++++++++++++------------------ 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 62190d7..427be90 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,6 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts -.env \ No newline at end of file +.env + +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index 43ad0e7..0e341d1 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,14 @@ - Copy `Project URL` to `DATABASE_URL` and `NEXT_PUBLIC_SUPABASE_URL` - Copy `Project API Keys: anon/public key` to `NEXT_PUBLIC_SUPABASE_ANON_KEY` 2. Run `yarn install && npx prisma generate` -3. Run `./dev.sh` (make sure have docker) +3. Run `./reset_db.sh` +4. Run `./dev.sh` (make sure have docker) +5. Disable email confirmation under Authentication > Providers > Email in Supabase UI +6. run `create_user.sh` in `scripts` folder + +Debugging: + +- make sure you are not ip banned on the supabase admin dashboard (if you failed to login) ## Resources diff --git a/package.json b/package.json index de51638..e49e8d9 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@dnd-kit/sortable": "^7.0.2", "@hookform/error-message": "^2.0.1", "@hookform/resolvers": "^3.1.0", - "@prisma/client": "4.14.1", + "@prisma/client": "^5.4.2", "@supabase/auth-helpers-nextjs": "^0.7.0", "@supabase/auth-helpers-react": "^0.4.0", "@supabase/supabase-js": "^2.23.0", @@ -62,7 +62,7 @@ "postcss-import": "^15.1.0", "prettier": "^2.8.8", "prettier-plugin-prisma": "^4.13.0", - "prisma": "4.14.1", + "prisma": "^5.4.2", "typescript-plugin-css-modules": "^5.0.1" } } diff --git a/yarn.lock b/yarn.lock index 024cf07..6cafc3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -762,22 +762,22 @@ picocolors "^1.0.0" tslib "^2.5.0" -"@prisma/client@4.14.1": - version "4.14.1" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.14.1.tgz#61720f385f687f7e88de41fccade1ed62be57a54" - integrity sha512-TZIswkeX1ccsHG/eN2kICzg/csXll0osK3EHu1QKd8VJ3XLcXozbNELKkCNfsCUvKJAwPdDtFCzF+O+raIVldw== +"@prisma/client@^5.4.2": + version "5.4.2" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.4.2.tgz#786f9c1d8f06d955933004ac638d14da4bf14025" + integrity sha512-2xsPaz4EaMKj1WS9iW6MlPhmbqtBsXAOeVttSePp8vTFTtvzh2hZbDgswwBdSCgPzmmwF+tLB259QzggvCmJqA== dependencies: - "@prisma/engines-version" "4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c" + "@prisma/engines-version" "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574" -"@prisma/engines-version@4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c": - version "4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c.tgz#0aeca447c4a5f23c83f68b8033e627b60bc01850" - integrity sha512-3jum8/YSudeSN0zGW5qkpz+wAN2V/NYCQ+BPjvHYDfWatLWlQkqy99toX0GysDeaUoBIJg1vaz2yKqiA3CFcQw== +"@prisma/engines-version@5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574": + version "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574.tgz#ff14f2926890edee47e8f1d08df7b4f392ee34bf" + integrity sha512-wvupDL4AA1vf4TQNANg7kR7y98ITqPsk6aacfBxZKtrJKRIsWjURHkZCGcQliHdqCiW/hGreO6d6ZuSv9MhdAA== -"@prisma/engines@4.14.1": - version "4.14.1" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.14.1.tgz#dac49f8d1f2d4f14a8ed7e6f96b24cd49bd6cd91" - integrity sha512-APqFddPVHYmWNKqc+5J5SqrLFfOghKOLZxobmguDUacxOwdEutLsbXPVhNnpFDmuQWQFbXmrTTPoRrrF6B1MWA== +"@prisma/engines@5.4.2": + version "5.4.2" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.4.2.tgz#ba2b7faeb227c76e423e88f962afe6a031319f3f" + integrity sha512-fqeucJ3LH0e1eyFdT0zRx+oETLancu5+n4lhiYECyEz6H2RDskPJHJYHkVc0LhkU4Uv7fuEnppKU3nVKNzMh8g== "@prisma/prisma-fmt-wasm@4.13.0-52.integration-mobc-upstream-d100a9299fcb9cffb064301998e9a94ce2722c49": version "4.13.0-52.integration-mobc-upstream-d100a9299fcb9cffb064301998e9a94ce2722c49" @@ -4437,12 +4437,12 @@ pretty-format@^29.0.0, pretty-format@^29.5.0: ansi-styles "^5.0.0" react-is "^18.0.0" -prisma@4.14.1: - version "4.14.1" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.14.1.tgz#7a6bb4ce847a9d08deabb6acdf3116fff15e1316" - integrity sha512-z6hxzTMYqT9SIKlzD08dhzsLUpxjFKKsLpp5/kBDnSqiOjtUyyl/dC5tzxLcOa3jkEHQ8+RpB/fE3w8bgNP51g== +prisma@^5.4.2: + version "5.4.2" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.4.2.tgz#7eac9276439ec7073ec697c6c0dfa259d96e955e" + integrity sha512-GDMZwZy7mysB2oXU+angQqJ90iaPFdD0rHaZNkn+dio5NRkGLmMqmXs31//tg/qXT3iB0cTQwnGGQNuirhSTZg== dependencies: - "@prisma/engines" "4.14.1" + "@prisma/engines" "5.4.2" prompt-sync@^4.2.0: version "4.2.0" From 9690f5f232420d438da31990790cf8057f0cbe20 Mon Sep 17 00:00:00 2001 From: bcheung Date: Tue, 24 Oct 2023 08:39:36 -0400 Subject: [PATCH 02/19] add react query devtools --- package.json | 1 + src/pages/_app.tsx | 3 +++ yarn.lock | 12 ++++++++++++ 3 files changed, 16 insertions(+) diff --git a/package.json b/package.json index e49e8d9..3fdd2fe 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@supabase/auth-helpers-react": "^0.4.0", "@supabase/supabase-js": "^2.23.0", "@tanstack/react-query": "^4.29.17", + "@tanstack/react-query-devtools": "^5.1.0", "@trpc/client": "^10.32.0", "@trpc/next": "^10.32.0", "@trpc/react-query": "^10.32.0", diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 21dc4be..eafdd22 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -12,6 +12,8 @@ import { SessionContextProvider } from "@supabase/auth-helpers-react" import { createPagesBrowserClient } from "@supabase/auth-helpers-nextjs" // trpc import { trpc } from "src/utils/trpc" +// react-query devTools +import { ReactQueryDevtools } from "@tanstack/react-query-devtools" export type NextPageWithLayout

= NextPage & { getLayout?: (page: ReactElement) => ReactNode @@ -31,6 +33,7 @@ function App({ Component, pageProps }: AppPropsWithLayout) { initialSession={pageProps.initialSession} > {getLayout()} + ) diff --git a/yarn.lock b/yarn.lock index 6cafc3e..e642792 100644 --- a/yarn.lock +++ b/yarn.lock @@ -889,6 +889,18 @@ resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.29.17.tgz#bbef3a30190732ae3554187702f6e34c5caaefd8" integrity sha512-iDbO8yZOpm1lqgq6L8mpxGbKaoiyZSjthxEB3WGU7mNPYss9q4H3Q67+e2xXGwkemEVmtEX/WwvtFitrvVU8TA== +"@tanstack/query-devtools@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.1.0.tgz#ad7e88a4e7e0813bc00a25f0a9284d22990fe204" + integrity sha512-EZhYS6clf4yyzFwE3b+7P2J46zgiweIwatc80MhfuzScz/Z4m1kPsKvNK0j54v4y1WvG4pN14qfOXjp4ac7f/Q== + +"@tanstack/react-query-devtools@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.1.0.tgz#1e75b4c0d5d8da657b8da97a61a229bf125935ef" + integrity sha512-Ms/GMccsrTBZQ+0v2pyIlaU0NlZXjhutPyhiQCviBqBbvYwsp/N/mT66YFaphzK/bhXzx5+NHbq8GI6V7KMY1Q== + dependencies: + "@tanstack/query-devtools" "5.1.0" + "@tanstack/react-query@^4.29.17": version "4.29.17" resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.29.17.tgz#bfd5da849bb7fd1b66eb9d47853ee030864a435e" From 48176af0f63a3bfed1b379a0525a793449be4365 Mon Sep 17 00:00:00 2001 From: bcheung Date: Wed, 25 Oct 2023 15:26:51 -0400 Subject: [PATCH 03/19] adds gutter --- src/components/MainLayout/TopNavbar.tsx | 131 +++++++++++++----------- src/components/UI/GutterContainer.tsx | 2 +- 2 files changed, 71 insertions(+), 62 deletions(-) diff --git a/src/components/MainLayout/TopNavbar.tsx b/src/components/MainLayout/TopNavbar.tsx index 3c1200b..e7cbba1 100644 --- a/src/components/MainLayout/TopNavbar.tsx +++ b/src/components/MainLayout/TopNavbar.tsx @@ -7,6 +7,7 @@ import PrimaryButton from "../UI/PrimaryButton" import SecondaryButton from "../UI/SecondaryButton" // types import { Position } from "src/types/PopupMenuTypes" +import GutterContainer from "../UI/GutterContainer" interface TopNavbarProps { onClickLogout: () => void @@ -35,68 +36,76 @@ export default function TopNavbar({ ref={anchorEl} className="sticky top-0 border-solid border-2 border-black shadow-neobrutShadow bg-background flex items-center justify-center w-full z-[100]" > -

-

- simplyworkouts -

- -
- -
- - - - - - - - + +
+

+ simplyworkouts +

+
- + +
+ + + + + + + + + + +
+
+
) } diff --git a/src/components/UI/GutterContainer.tsx b/src/components/UI/GutterContainer.tsx index 4f65bb2..1efa781 100644 --- a/src/components/UI/GutterContainer.tsx +++ b/src/components/UI/GutterContainer.tsx @@ -7,7 +7,7 @@ interface GutterContainerProps { export default function GutterContainer({ children }: GutterContainerProps) { return (
-
{children}
+
{children}
) } From 3d806742546dc80f41ac4c54533faee93f1e1741 Mon Sep 17 00:00:00 2001 From: bcheung Date: Sat, 4 Nov 2023 00:06:28 -0400 Subject: [PATCH 04/19] update login ui --- next.config.js | 6 +++++ src/components/UI/CopyIcon.tsx | 40 +++++++++++++++++++++++++++++ src/components/UI/FormInput.tsx | 4 +-- src/pages/login.tsx | 45 ++++++++++++++++++++++----------- 4 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 src/components/UI/CopyIcon.tsx diff --git a/next.config.js b/next.config.js index a890b72..afc372e 100644 --- a/next.config.js +++ b/next.config.js @@ -16,6 +16,12 @@ const nextConfig = { port: "", pathname: "/**", }, + { + protocol: "https", + hostname: "cdn-icons-png.flaticon.com", + port: "", + pathname: "/**", + }, ], }, } diff --git a/src/components/UI/CopyIcon.tsx b/src/components/UI/CopyIcon.tsx new file mode 100644 index 0000000..a775482 --- /dev/null +++ b/src/components/UI/CopyIcon.tsx @@ -0,0 +1,40 @@ +import Image from "next/image" +import React, { useState } from "react" + +// https://cdn-icons-png.flaticon.com/512/126/126498.png + +interface CopyIconProps { + value: string + label: string +} + +export default function CopyIcon({ value, label }: CopyIconProps) { + const [isCopied, setIsCopied] = useState(false) + + const handleClick = () => { + navigator.clipboard.writeText(value) + setIsCopied(true) + setTimeout(() => { + setIsCopied(false) + }, 1000) + } + + return ( +
+

{label}

+ flaticon-copy-icon.png + {isCopied && ( +

+ Copied! +

+ )} +
+ ) +} diff --git a/src/components/UI/FormInput.tsx b/src/components/UI/FormInput.tsx index 087c94b..309f94f 100644 --- a/src/components/UI/FormInput.tsx +++ b/src/components/UI/FormInput.tsx @@ -29,12 +29,12 @@ export default function FormInput({ render={({ field: { onChange, value, name }, fieldState: { error } }) => (
-
+ +
+ ) } From fac3f276961bf464f18490c1d6bd5199b92cd02e Mon Sep 17 00:00:00 2001 From: bcheung Date: Sat, 4 Nov 2023 00:10:26 -0400 Subject: [PATCH 05/19] add demo user --- src/pages/login.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/login.tsx b/src/pages/login.tsx index 1d3c5c0..af8ea6c 100644 --- a/src/pages/login.tsx +++ b/src/pages/login.tsx @@ -81,7 +81,10 @@ export default function Login() {

Demo Account

- +
From 9054b47e2e4f77931f52a6f08476338b6b14b672 Mon Sep 17 00:00:00 2001 From: bcheung Date: Sat, 4 Nov 2023 12:44:59 -0400 Subject: [PATCH 06/19] moves files, adds gym location to db --- README.md | 14 ++++++++------ dev.sh | 3 --- logs.sh | 2 -- prisma/db-diagram-io.dbml | 1 + .../migration.sql | 12 ++++++++++++ prisma/schema.prisma | 1 + scripts/dev.sh | 5 +++++ scripts/logs.sh | 4 ++++ pre_PR.sh => scripts/pre_PR.sh | 2 ++ reset_db.sh => scripts/reset_db.sh | 2 ++ restart.sh => scripts/restart.sh | 2 ++ stop.sh => scripts/stop.sh | 2 ++ update_db.sh => scripts/update_db.sh | 2 ++ 13 files changed, 41 insertions(+), 11 deletions(-) delete mode 100644 dev.sh delete mode 100644 logs.sh create mode 100644 prisma/migrations/20231104164111_add_gym_location_to_workout_plan_table/migration.sql create mode 100644 scripts/dev.sh create mode 100644 scripts/logs.sh rename pre_PR.sh => scripts/pre_PR.sh (65%) rename reset_db.sh => scripts/reset_db.sh (80%) rename restart.sh => scripts/restart.sh (60%) rename stop.sh => scripts/stop.sh (60%) rename update_db.sh => scripts/update_db.sh (65%) diff --git a/README.md b/README.md index 0e341d1..9239fee 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,6 @@ prisma -
- ## Workout App - [ ] Create your own workout plan @@ -20,8 +18,6 @@ - [ ] Track your workouts - [ ] Track your progress (BMI, weight etc.) -
- ## Getting Started 1. Configure `.env` file @@ -38,12 +34,18 @@ Debugging: - make sure you are not ip banned on the supabase admin dashboard (if you failed to login) +## Prisma + +https://www.prisma.io/docs/concepts/components/prisma-migrate/mental-model + +`prisma migrate dev` for development environment + +`prisma migrate deploy` for deployment (PRD) environment + ## Resources - [Figma](https://www.figma.com/file/R0i3v0IsjhkOhDSYITeWHU/Workout-App?type=design&node-id=0%3A1&mode=design&t=4R5sghDXxfNWkufE-1) -
- ## Notes to self - index.tsx is the 'container' diff --git a/dev.sh b/dev.sh deleted file mode 100644 index 930513e..0000000 --- a/dev.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -docker compose -f docker-compose.dev.yml up --build --detach -./logs.sh diff --git a/logs.sh b/logs.sh deleted file mode 100644 index 8d515ae..0000000 --- a/logs.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -docker compose -f ./docker-compose.dev.yml logs --timestamps --follow \ No newline at end of file diff --git a/prisma/db-diagram-io.dbml b/prisma/db-diagram-io.dbml index f497ae4..ea6920d 100644 --- a/prisma/db-diagram-io.dbml +++ b/prisma/db-diagram-io.dbml @@ -79,6 +79,7 @@ Table WorkoutPlan { exerciseOrder "string[]" name string userId string [unique] + gymLocation string [unique] } Table RunPlan { diff --git a/prisma/migrations/20231104164111_add_gym_location_to_workout_plan_table/migration.sql b/prisma/migrations/20231104164111_add_gym_location_to_workout_plan_table/migration.sql new file mode 100644 index 0000000..a982d0a --- /dev/null +++ b/prisma/migrations/20231104164111_add_gym_location_to_workout_plan_table/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - A unique constraint covering the columns `[gymLocation]` on the table `WorkoutPlan` will be added. If there are existing duplicate values, this will fail. + - Added the required column `gymLocation` to the `WorkoutPlan` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "WorkoutPlan" ADD COLUMN "gymLocation" TEXT NOT NULL; + +-- CreateIndex +CREATE UNIQUE INDEX "WorkoutPlan_gymLocation_key" ON "WorkoutPlan"("gymLocation"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 028cb2b..5383a30 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -130,6 +130,7 @@ model WorkoutPlan { createdAt DateTime @default(now()) name String exerciseOrder String[] + gymLocation String @unique lastWorkout DateTime? duration Int? // relations diff --git a/scripts/dev.sh b/scripts/dev.sh new file mode 100644 index 0000000..cf16a75 --- /dev/null +++ b/scripts/dev.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# dev docker compose setup +docker compose -f ../docker-compose.dev.yml up --build --detach +./logs.sh diff --git a/scripts/logs.sh b/scripts/logs.sh new file mode 100644 index 0000000..00c389c --- /dev/null +++ b/scripts/logs.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +# dev logs +docker compose -f ../docker-compose.dev.yml logs --timestamps --follow \ No newline at end of file diff --git a/pre_PR.sh b/scripts/pre_PR.sh similarity index 65% rename from pre_PR.sh rename to scripts/pre_PR.sh index efdf026..99a0f35 100644 --- a/pre_PR.sh +++ b/scripts/pre_PR.sh @@ -1,3 +1,5 @@ #!/usr/bin/env bash + +# if i feel like it yarn lint yarn jest-ci \ No newline at end of file diff --git a/reset_db.sh b/scripts/reset_db.sh similarity index 80% rename from reset_db.sh rename to scripts/reset_db.sh index a77178b..deb4b1d 100644 --- a/reset_db.sh +++ b/scripts/reset_db.sh @@ -1,2 +1,4 @@ #!/usr/bin/env bash + +# wipe db yarn prisma migrate reset \ No newline at end of file diff --git a/restart.sh b/scripts/restart.sh similarity index 60% rename from restart.sh rename to scripts/restart.sh index 8ffe29e..07ac5cb 100644 --- a/restart.sh +++ b/scripts/restart.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash + +# restart docker compose setup ./stop.sh ./dev.sh ./logs.sh \ No newline at end of file diff --git a/stop.sh b/scripts/stop.sh similarity index 60% rename from stop.sh rename to scripts/stop.sh index c48171f..4b2a54a 100644 --- a/stop.sh +++ b/scripts/stop.sh @@ -1,2 +1,4 @@ #!/usr/bin/env bash + +# stop all containers and remove all containers docker stop $(docker ps -aq) && docker rm $(docker ps -aq) \ No newline at end of file diff --git a/update_db.sh b/scripts/update_db.sh similarity index 65% rename from update_db.sh rename to scripts/update_db.sh index daa9361..5cfd1e6 100644 --- a/update_db.sh +++ b/scripts/update_db.sh @@ -1,3 +1,5 @@ #!/usr/bin/env bash + +# development only migrate db yarn prisma migrate dev yarn prisma generate \ No newline at end of file From dc35d55b848ed95e13bc502a8895fc3afae15e23 Mon Sep 17 00:00:00 2001 From: bcheung Date: Sat, 4 Nov 2023 12:58:06 -0400 Subject: [PATCH 07/19] update schema --- README.md | 2 ++ prisma/db-diagram-io.dbml | 9 ++++++++- prisma/schema.prisma | 18 +++++++++++++----- src/components/CreateWorkout/index.tsx | 4 +--- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9239fee..1c994ab 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ https://www.prisma.io/docs/concepts/components/prisma-migrate/mental-model `prisma migrate deploy` for deployment (PRD) environment +- Edit the DBML documentation before editing the schema + ## Resources - [Figma](https://www.figma.com/file/R0i3v0IsjhkOhDSYITeWHU/Workout-App?type=design&node-id=0%3A1&mode=design&t=4R5sghDXxfNWkufE-1) diff --git a/prisma/db-diagram-io.dbml b/prisma/db-diagram-io.dbml index ea6920d..6149ee3 100644 --- a/prisma/db-diagram-io.dbml +++ b/prisma/db-diagram-io.dbml @@ -79,7 +79,6 @@ Table WorkoutPlan { exerciseOrder "string[]" name string userId string [unique] - gymLocation string [unique] } Table RunPlan { @@ -120,5 +119,13 @@ Table Activity { userId string } +Table GymLocation { + gymId string [pk, unique, default: 'uuid()', ref: - WorkoutPlan.planId] + name string [unique] +} + + + +Ref: "WorkoutPlan"."planId" < "WorkoutPlan"."exerciseOrder" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5383a30..6b3219f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -125,17 +125,18 @@ model Set { } model WorkoutPlan { - planId String @id @unique @default(uuid()) @db.Uuid - updatedAt DateTime @default(now()) @updatedAt - createdAt DateTime @default(now()) + planId String @id @unique @default(uuid()) @db.Uuid + updatedAt DateTime @default(now()) @updatedAt + createdAt DateTime @default(now()) name String exerciseOrder String[] - gymLocation String @unique lastWorkout DateTime? duration Int? // relations - user User @relation(fields: [userId], references: [userId]) + user User @relation(fields: [userId], references: [userId]) userId String + gym GymLocation @relation(fields: [gymId], references: [gymId]) + gymId String exercises Exercise[] sessions Session[] @@ -188,3 +189,10 @@ model Run { runExercise RunExercise @relation(fields: [exerciseId], references: [exerciseId]) exerciseId String @db.Uuid } + +model GymLocation { + gymId String @id @unique @default(uuid()) @db.Uuid + name String @unique + // relation + workoutPlans WorkoutPlan[] +} diff --git a/src/components/CreateWorkout/index.tsx b/src/components/CreateWorkout/index.tsx index 209c02c..ff1fccf 100644 --- a/src/components/CreateWorkout/index.tsx +++ b/src/components/CreateWorkout/index.tsx @@ -97,9 +97,7 @@ export default function CreateWorkout({ exercises }: CreateWorkoutProps) { onSubmit={handleSubmit(handleSubmitForm)} className="flex flex-col gap-6 justify-center" > -
- -
+ Date: Sat, 4 Nov 2023 19:24:26 -0400 Subject: [PATCH 08/19] update gym location relations --- prisma/db-diagram-io.dbml | 5 ++-- .../migration.sql | 27 +++++++++++++++++++ prisma/schema.prisma | 20 +++++++------- 3 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 prisma/migrations/20231104232149_update_gym_location_relations/migration.sql diff --git a/prisma/db-diagram-io.dbml b/prisma/db-diagram-io.dbml index 6149ee3..fff5d8c 100644 --- a/prisma/db-diagram-io.dbml +++ b/prisma/db-diagram-io.dbml @@ -120,8 +120,9 @@ Table Activity { } Table GymLocation { - gymId string [pk, unique, default: 'uuid()', ref: - WorkoutPlan.planId] - name string [unique] + gymId string [pk, unique, default: 'uuid()', ref: > User.userId] + name string + description string } diff --git a/prisma/migrations/20231104232149_update_gym_location_relations/migration.sql b/prisma/migrations/20231104232149_update_gym_location_relations/migration.sql new file mode 100644 index 0000000..c2a9cb0 --- /dev/null +++ b/prisma/migrations/20231104232149_update_gym_location_relations/migration.sql @@ -0,0 +1,27 @@ +/* + Warnings: + + - You are about to drop the column `gymLocation` on the `WorkoutPlan` table. All the data in the column will be lost. + +*/ +-- DropIndex +DROP INDEX "WorkoutPlan_gymLocation_key"; + +-- AlterTable +ALTER TABLE "WorkoutPlan" DROP COLUMN "gymLocation"; + +-- CreateTable +CREATE TABLE "GymLocation" ( + "gymId" UUID NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT NOT NULL, + "userId" TEXT NOT NULL, + + CONSTRAINT "GymLocation_pkey" PRIMARY KEY ("gymId") +); + +-- CreateIndex +CREATE UNIQUE INDEX "GymLocation_gymId_key" ON "GymLocation"("gymId"); + +-- AddForeignKey +ALTER TABLE "GymLocation" ADD CONSTRAINT "GymLocation_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("userId") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6b3219f..3631fbe 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -26,6 +26,7 @@ model User { profile Profile? runPlans RunPlan[] activities Activity[] + gymLocations GymLocation[] } // Profile Model Table @@ -125,18 +126,16 @@ model Set { } model WorkoutPlan { - planId String @id @unique @default(uuid()) @db.Uuid - updatedAt DateTime @default(now()) @updatedAt - createdAt DateTime @default(now()) + planId String @id @unique @default(uuid()) @db.Uuid + updatedAt DateTime @default(now()) @updatedAt + createdAt DateTime @default(now()) name String exerciseOrder String[] lastWorkout DateTime? duration Int? // relations - user User @relation(fields: [userId], references: [userId]) + user User @relation(fields: [userId], references: [userId]) userId String - gym GymLocation @relation(fields: [gymId], references: [gymId]) - gymId String exercises Exercise[] sessions Session[] @@ -191,8 +190,11 @@ model Run { } model GymLocation { - gymId String @id @unique @default(uuid()) @db.Uuid - name String @unique + gymId String @id @unique @default(uuid()) @db.Uuid + name String + description String + // relation - workoutPlans WorkoutPlan[] + gymLocation User @relation(fields: [userId], references: [userId]) + userId String } From 2a6f4c723565c73392d9170ce7b32aa4766720a0 Mon Sep 17 00:00:00 2001 From: bcheung Date: Sat, 4 Nov 2023 20:52:02 -0400 Subject: [PATCH 09/19] makes description optional --- .../migration.sql | 2 ++ prisma/schema.prisma | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 prisma/migrations/20231105001258_add_optional_field_for_gym_location/migration.sql diff --git a/prisma/migrations/20231105001258_add_optional_field_for_gym_location/migration.sql b/prisma/migrations/20231105001258_add_optional_field_for_gym_location/migration.sql new file mode 100644 index 0000000..6a9e691 --- /dev/null +++ b/prisma/migrations/20231105001258_add_optional_field_for_gym_location/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "GymLocation" ALTER COLUMN "description" DROP NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 3631fbe..edd964d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -190,11 +190,11 @@ model Run { } model GymLocation { - gymId String @id @unique @default(uuid()) @db.Uuid + gymId String @id @unique @default(uuid()) @db.Uuid name String - description String + description String? // relation - gymLocation User @relation(fields: [userId], references: [userId]) - userId String + user User @relation(fields: [userId], references: [userId]) + userId String } From 4280fa646c45ddb9ad5cb39e0019eba5607180b5 Mon Sep 17 00:00:00 2001 From: bcheung Date: Sat, 4 Nov 2023 20:53:45 -0400 Subject: [PATCH 10/19] adds add gym location and adds get gym locations --- .../CreateWorkout/AddGymLocationModal.tsx | 63 +++++++++++++++++++ src/components/CreateWorkout/index.tsx | 54 ++++++++++++++-- src/components/UI/FormSelect.tsx | 57 +++++++++++++++++ src/hooks/useGetGymLocations.tsx | 5 ++ src/hooks/useMutationAddGymLocation.tsx | 19 ++++++ src/pages/workouts/create-workout.tsx | 10 ++- .../add-gym-location.ts | 31 +++++++++ .../get-gym-location.ts | 26 ++++++++ src/server/routers/_app.ts | 2 + src/server/routers/gym-location-router.ts | 11 ++++ src/types/menu.ts | 5 ++ src/types/trpc/router-types.ts | 10 +++ src/validators/workout-schema.ts | 6 ++ 13 files changed, 293 insertions(+), 6 deletions(-) create mode 100644 src/components/CreateWorkout/AddGymLocationModal.tsx create mode 100644 src/components/UI/FormSelect.tsx create mode 100644 src/hooks/useGetGymLocations.tsx create mode 100644 src/hooks/useMutationAddGymLocation.tsx create mode 100644 src/server/procedures/gym-location-procedures/add-gym-location.ts create mode 100644 src/server/procedures/gym-location-procedures/get-gym-location.ts create mode 100644 src/server/routers/gym-location-router.ts create mode 100644 src/types/menu.ts diff --git a/src/components/CreateWorkout/AddGymLocationModal.tsx b/src/components/CreateWorkout/AddGymLocationModal.tsx new file mode 100644 index 0000000..4c1b2f8 --- /dev/null +++ b/src/components/CreateWorkout/AddGymLocationModal.tsx @@ -0,0 +1,63 @@ +import React from "react" +// react-hook-forms +import { useForm, FieldValues } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +// z +import { z } from "zod" +// types/validators +import { AddGymLocationSchema } from "src/validators/workout-schema" +import FormInput from "../UI/FormInput" +import PrimaryButton from "../UI/PrimaryButton" +// hooks +import useMutationAddGymLocation from "src/hooks/useMutationAddGymLocation" +import useToastMessage, { ToastMessage } from "src/hooks/useToastMessage" + +interface AddGymLocationModalProps { + // trigger setValues in the parent form for the new location from getValues().name in this form + onSuccessAdd: (gymName: string) => void +} + +export default function AddGymLocationModal({ + onSuccessAdd, +}: AddGymLocationModalProps) { + const toastMessage = useToastMessage() + + const { handleSubmit, reset, control } = useForm< + z.infer & FieldValues + >({ + defaultValues: { + description: "", + name: "", + }, + resolver: zodResolver(AddGymLocationSchema), + }) + + const { mutate, isLoading } = useMutationAddGymLocation( + (data) => { + toastMessage("Successfully added gym location.", ToastMessage.Success) + onSuccessAdd(data.gymId) + reset() + }, + () => toastMessage("Error adding gym location.", ToastMessage.Error) + ) + + const handleSubmitForm = (data: z.infer) => { + mutate(data) + } + + return ( +
+ + + + + ) +} diff --git a/src/components/CreateWorkout/index.tsx b/src/components/CreateWorkout/index.tsx index ff1fccf..2585f56 100644 --- a/src/components/CreateWorkout/index.tsx +++ b/src/components/CreateWorkout/index.tsx @@ -11,6 +11,8 @@ import { verticalListSortingStrategy } from "@dnd-kit/sortable" import useToastMessage, { ToastMessage } from "src/hooks/useToastMessage" import useDragSorting from "src/hooks/useDragSorting" import useMutationAddWorkoutPlan from "src/hooks/useMutationAddWorkoutPlan" +// types +import WorkoutPlanSchema from "src/validators/workout-schema" // components import FormErrMsg from "../UI/FormErrorMsg" import DragSortable from "../UI/DragSortable" @@ -25,17 +27,23 @@ import AddExercise from "./AddExercise" import { GetExercisesOutput } from "src/types/trpc/router-types" import exerciseHash from "src/utils/exercises-hashmap" import BorderCard from "../UI/BorderCard" -// types -import WorkoutPlanSchema from "src/validators/workout-schema" +import FormSelect from "../UI/FormSelect" +import AddGymLocationModal from "./AddGymLocationModal" +import { GetGymLocationsOutput } from "src/types/trpc/router-types" interface CreateWorkoutProps { exercises: GetExercisesOutput | undefined + gymLocations: GetGymLocationsOutput | undefined } -export default function CreateWorkout({ exercises }: CreateWorkoutProps) { +export default function CreateWorkout({ + exercises, + gymLocations, +}: CreateWorkoutProps) { const router = useRouter() const toastMessage = useToastMessage() const [isAddExercise, setIsAddExercise] = useState(false) + const [isAddGymLocation, setIsAddGymLocation] = useState(false) const { items, handleDragEnd, setItems } = useDragSorting([]) const { handleSubmit, @@ -43,9 +51,11 @@ export default function CreateWorkout({ exercises }: CreateWorkoutProps) { control, formState: { errors }, setValue, + watch, } = useForm & FieldValues>({ defaultValues: { name: "", + gymLocation: "", exercises: {}, exerciseOrder: [], }, @@ -80,7 +90,10 @@ export default function CreateWorkout({ exercises }: CreateWorkoutProps) { } const handleCloseModal = () => setIsAddExercise(false) - + const handleCloseAddGymLocation = () => { + setIsAddGymLocation(false) + setValue("gymLocation", "") + } const handleClickAdd = (exerciseId: string) => setItems([...items, exerciseId]) @@ -89,6 +102,16 @@ export default function CreateWorkout({ exercises }: CreateWorkoutProps) { setValue("exerciseOrder", items as string[]) }, [items, setValue]) + // if addGymLocation is selected then open popup dialog + useEffect(() => { + const subscription = watch((value) => { + if (value.gymLocation === "addGymLocation") { + setIsAddGymLocation(true) + } + }) + return () => subscription.unsubscribe() + }, [watch]) + return (
@@ -98,6 +121,17 @@ export default function CreateWorkout({ exercises }: CreateWorkoutProps) { className="flex flex-col gap-6 justify-center" > + ({ + id: gymId, + name: name, + value: gymId, + })) || [] + } + /> + + { + setValue("gymLocation", gymLocation) + setIsAddGymLocation(false) + }} + /> +
) } diff --git a/src/components/UI/FormSelect.tsx b/src/components/UI/FormSelect.tsx new file mode 100644 index 0000000..02d935b --- /dev/null +++ b/src/components/UI/FormSelect.tsx @@ -0,0 +1,57 @@ +import React from "react" +import { FieldValues, UseControllerProps, Controller } from "react-hook-form" +import { startCase } from "lodash" +// types +import { MenuOption } from "src/types/menu" + +interface FormSelectProps { + name: UseControllerProps["name"] + control: UseControllerProps["control"] + label?: string + menuOptions: MenuOption[] | [] +} + +export default function FormSelect({ + name, + control, + label, + menuOptions, +}: FormSelectProps) { + return ( + ( +
+ + + {error &&

{error.message}

} +
+ )} + /> + ) +} diff --git a/src/hooks/useGetGymLocations.tsx b/src/hooks/useGetGymLocations.tsx new file mode 100644 index 0000000..a78e99f --- /dev/null +++ b/src/hooks/useGetGymLocations.tsx @@ -0,0 +1,5 @@ +import { trpc } from "src/utils/trpc" + +export default function useGetGymLocations() { + return trpc.gymLocations.getGymLocations.useQuery() +} diff --git a/src/hooks/useMutationAddGymLocation.tsx b/src/hooks/useMutationAddGymLocation.tsx new file mode 100644 index 0000000..c94381a --- /dev/null +++ b/src/hooks/useMutationAddGymLocation.tsx @@ -0,0 +1,19 @@ +import { trpc } from "src/utils/trpc" +import { CreateGymLocationOutput } from "src/types/trpc/router-types" + +export default function useMutationAddGymLocation( + onSuccess: (data: CreateGymLocationOutput) => void, + onError: () => void +) { + const utils = trpc.useContext() + + return trpc.gymLocations.addGymLocation.useMutation({ + onSuccess: (data) => { + onSuccess(data) + utils.gymLocations.invalidate() + }, + onError: () => { + onError() + }, + }) +} diff --git a/src/pages/workouts/create-workout.tsx b/src/pages/workouts/create-workout.tsx index 02ca552..8baf2a9 100644 --- a/src/pages/workouts/create-workout.tsx +++ b/src/pages/workouts/create-workout.tsx @@ -6,14 +6,20 @@ import Fade from "src/components/UI/transitions/Fade" import LoadingSpinner from "src/components/UI/LoadingSpinner" // hooks import useGetExercises from "src/hooks/useGetExercises" +import useGetGymLocations from "src/hooks/useGetGymLocations" export default function CreateWorkoutPage() { - const { data: exercises, isLoading } = useGetExercises() + const { data: exercises, isLoading: isLoadingExercises } = useGetExercises() + const { data: gymLocations, isLoading: isLoadingGymLocations } = + useGetGymLocations() + + const isLoading = isLoadingExercises || isLoadingGymLocations + return isLoading ? ( ) : ( - + ) } diff --git a/src/server/procedures/gym-location-procedures/add-gym-location.ts b/src/server/procedures/gym-location-procedures/add-gym-location.ts new file mode 100644 index 0000000..c1c8c58 --- /dev/null +++ b/src/server/procedures/gym-location-procedures/add-gym-location.ts @@ -0,0 +1,31 @@ +import { TRPCError } from "@trpc/server" +import { tProtectedProcedure } from "src/server/trpc" +import prisma from "src/utils/prisma" +import { AddGymLocationSchema } from "src/validators/workout-schema" + +const addGymLocation = tProtectedProcedure + .input(AddGymLocationSchema) + .mutation(async ({ input: { name, description }, ctx: { user } }) => { + try { + const gymLocation = prisma.gymLocation.create({ + data: { + name, + description, + user: { + connect: { + userId: user.id, + }, + }, + }, + }) + return gymLocation + } catch (error) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Error adding gym location.", + cause: error, + }) + } + }) + +export default addGymLocation diff --git a/src/server/procedures/gym-location-procedures/get-gym-location.ts b/src/server/procedures/gym-location-procedures/get-gym-location.ts new file mode 100644 index 0000000..5258484 --- /dev/null +++ b/src/server/procedures/gym-location-procedures/get-gym-location.ts @@ -0,0 +1,26 @@ +import { TRPCError } from "@trpc/server" +import { tProtectedProcedure } from "src/server/trpc" +import prisma from "src/utils/prisma" + +const getGymLocations = tProtectedProcedure.query(async ({ ctx: { user } }) => { + try { + const gymLocations = await prisma.gymLocation.findMany({ + where: { + userId: user.id, + }, + orderBy: { + name: "asc", + }, + }) + + return gymLocations + } catch (error) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Error getting exercises for user.", + cause: error, + }) + } +}) + +export default getGymLocations diff --git a/src/server/routers/_app.ts b/src/server/routers/_app.ts index 3f3e4a7..b42a698 100644 --- a/src/server/routers/_app.ts +++ b/src/server/routers/_app.ts @@ -3,12 +3,14 @@ import { trouter, tprocedure } from "../trpc" import userRouter from "./user-router" import statsRouter from "./stats-router" import workoutsRouter from "./workouts-router" +import gymLocationRouter from "./gym-location-router" export const appRouter = trouter({ health: tprocedure.query(() => "healthy"), user: userRouter, stats: statsRouter, workouts: workoutsRouter, + gymLocations: gymLocationRouter, }) // export type definition of API diff --git a/src/server/routers/gym-location-router.ts b/src/server/routers/gym-location-router.ts new file mode 100644 index 0000000..b5721f0 --- /dev/null +++ b/src/server/routers/gym-location-router.ts @@ -0,0 +1,11 @@ +import { trouter } from "../trpc" +// procedures +import addGymLocation from "../procedures/gym-location-procedures/add-gym-location" +import getGymLocations from "../procedures/gym-location-procedures/get-gym-location" + +const gymLocationRouter = trouter({ + addGymLocation, + getGymLocations, +}) + +export default gymLocationRouter diff --git a/src/types/menu.ts b/src/types/menu.ts new file mode 100644 index 0000000..e9b809c --- /dev/null +++ b/src/types/menu.ts @@ -0,0 +1,5 @@ +export interface MenuOption { + id: string | number + name: string + value: string | number +} diff --git a/src/types/trpc/router-types.ts b/src/types/trpc/router-types.ts index 6771e51..6a75706 100644 --- a/src/types/trpc/router-types.ts +++ b/src/types/trpc/router-types.ts @@ -17,3 +17,13 @@ export type StatsOutput = RouterOutput["stats"]["getStats"] export type GetWorkoutPlansOutput = RouterOutput["workouts"]["getWorkoutPlans"] export type GetExercisesOutput = RouterOutput["workouts"]["getExercises"] + +/* -------------------------------------------------------------------------- */ +/* Gym Locations Router */ +/* -------------------------------------------------------------------------- */ + +export type CreateGymLocationOutput = + RouterOutput["gymLocations"]["addGymLocation"] + +export type GetGymLocationsOutput = + RouterOutput["gymLocations"]["getGymLocations"] diff --git a/src/validators/workout-schema.ts b/src/validators/workout-schema.ts index 3de1bf3..700cb2b 100644 --- a/src/validators/workout-schema.ts +++ b/src/validators/workout-schema.ts @@ -9,6 +9,7 @@ const WorkoutPlanSchema = z .object({ name: z.string().min(1), exercises: z.record(z.string(), TargetSchema.required()), + gymLocation: z.string().min(1), exerciseOrder: z .array(z.string()) .min(1, { message: "At least one exercise required." }), @@ -26,3 +27,8 @@ export const UpdatePlanSchema = WorkoutPlanSchema.extend({ }) ), }) + +export const AddGymLocationSchema = z.object({ + name: z.string().min(1), + description: z.string().nullable(), +}) From a26202bd9197df2ab55d8ff31b89c35988f23fbb Mon Sep 17 00:00:00 2001 From: bcheung Date: Sat, 4 Nov 2023 21:02:52 -0400 Subject: [PATCH 11/19] adds one gym location to many workoutplan relationship to workout plan table --- prisma/db-diagram-io.dbml | 5 +++-- .../migration.sql | 11 +++++++++++ prisma/schema.prisma | 12 ++++++++---- .../workouts-procedures/add-workout-plan.ts | 10 +++++++++- 4 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 prisma/migrations/20231105005838_add_gym_location_column_to_workout_plan_table/migration.sql diff --git a/prisma/db-diagram-io.dbml b/prisma/db-diagram-io.dbml index fff5d8c..d9648a5 100644 --- a/prisma/db-diagram-io.dbml +++ b/prisma/db-diagram-io.dbml @@ -79,6 +79,7 @@ Table WorkoutPlan { exerciseOrder "string[]" name string userId string [unique] + gymLocationId string [unique] } Table RunPlan { @@ -120,9 +121,9 @@ Table Activity { } Table GymLocation { - gymId string [pk, unique, default: 'uuid()', ref: > User.userId] + gymId string [pk, unique, default: 'uuid()', ref: > User.userId, ref: < WorkoutPlan.gymLocationId] name string - description string + description string [note: 'optional'] } diff --git a/prisma/migrations/20231105005838_add_gym_location_column_to_workout_plan_table/migration.sql b/prisma/migrations/20231105005838_add_gym_location_column_to_workout_plan_table/migration.sql new file mode 100644 index 0000000..77d9ba0 --- /dev/null +++ b/prisma/migrations/20231105005838_add_gym_location_column_to_workout_plan_table/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - Added the required column `gymId` to the `WorkoutPlan` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "WorkoutPlan" ADD COLUMN "gymId" UUID NOT NULL; + +-- AddForeignKey +ALTER TABLE "WorkoutPlan" ADD CONSTRAINT "WorkoutPlan_gymId_fkey" FOREIGN KEY ("gymId") REFERENCES "GymLocation"("gymId") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index edd964d..4be10a1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -126,16 +126,18 @@ model Set { } model WorkoutPlan { - planId String @id @unique @default(uuid()) @db.Uuid - updatedAt DateTime @default(now()) @updatedAt - createdAt DateTime @default(now()) + planId String @id @unique @default(uuid()) @db.Uuid + updatedAt DateTime @default(now()) @updatedAt + createdAt DateTime @default(now()) name String exerciseOrder String[] lastWorkout DateTime? duration Int? // relations - user User @relation(fields: [userId], references: [userId]) + user User @relation(fields: [userId], references: [userId]) userId String + gymLocation GymLocation @relation(fields: [gymId], references: [gymId]) + gymId String @db.Uuid exercises Exercise[] sessions Session[] @@ -197,4 +199,6 @@ model GymLocation { // relation user User @relation(fields: [userId], references: [userId]) userId String + + workoutPlans WorkoutPlan[] } diff --git a/src/server/procedures/workouts-procedures/add-workout-plan.ts b/src/server/procedures/workouts-procedures/add-workout-plan.ts index 39a35f9..74de953 100644 --- a/src/server/procedures/workouts-procedures/add-workout-plan.ts +++ b/src/server/procedures/workouts-procedures/add-workout-plan.ts @@ -6,7 +6,10 @@ import WorkoutPlanSchema from "src/validators/workout-schema" const addWorkoutPlan = tProtectedProcedure .input(WorkoutPlanSchema) .mutation( - async ({ input: { name, exerciseOrder, exercises }, ctx: { user } }) => { + async ({ + input: { name, exerciseOrder, exercises, gymLocation }, + ctx: { user }, + }) => { try { const exerciseIdArr = Object.keys(exercises) // create workoutplan @@ -22,6 +25,11 @@ const addWorkoutPlan = tProtectedProcedure userId: user.id, }, }, + gymLocation: { + connect: { + gymId: gymLocation, + }, + }, }, }) From d6635f6ce9c2e94f8bae0fe7ef1ed4551d5f4a4f Mon Sep 17 00:00:00 2001 From: bcheung Date: Sat, 4 Nov 2023 21:07:35 -0400 Subject: [PATCH 12/19] adds gym location to workout plans card --- src/components/Workouts/index.tsx | 104 ++++++++++-------- .../workouts-procedures/get-workout-plans.ts | 5 + 2 files changed, 63 insertions(+), 46 deletions(-) diff --git a/src/components/Workouts/index.tsx b/src/components/Workouts/index.tsx index 63e0890..8a8c8ab 100644 --- a/src/components/Workouts/index.tsx +++ b/src/components/Workouts/index.tsx @@ -68,57 +68,69 @@ export default function Workouts({ plans }: WorkoutsProps) { /> {plans && plans.length > 0 ? ( - plans.map(({ planId, name, lastWorkout, duration }) => ( - -
- + plans.map( + ({ planId, name, lastWorkout, duration, gymLocation }) => ( +
- + + + +
+
+
+ { + setSelectedPlanId(planId) + setIsEdit(true) + }} + /> + { + setSelectedPlanId(planId) + setIsConfirmDelete(true) + }} />
- -
- { - setSelectedPlanId(planId) - setIsEdit(true) - }} - /> - { - setSelectedPlanId(planId) - setIsConfirmDelete(true) - }} - /> -
-
- )) + + ) + ) ) : ( router.push("/workouts/create-workout")} diff --git a/src/server/procedures/workouts-procedures/get-workout-plans.ts b/src/server/procedures/workouts-procedures/get-workout-plans.ts index 8476fd0..32db2ae 100644 --- a/src/server/procedures/workouts-procedures/get-workout-plans.ts +++ b/src/server/procedures/workouts-procedures/get-workout-plans.ts @@ -14,6 +14,11 @@ const getWorkoutPlans = tProtectedProcedure.query(async ({ ctx: { user } }) => { exercise: true, }, }, + gymLocation: { + select: { + name: true, + }, + }, }, }) From bef741bd513e3be28091eb0ab09f509bea7b502c Mon Sep 17 00:00:00 2001 From: bcheung Date: Sat, 4 Nov 2023 21:53:24 -0400 Subject: [PATCH 13/19] adds gymlocation crud page --- src/components/GymLocations/index.tsx | 61 +++++++++++++++++++++++++ src/components/MainLayout/TopNavbar.tsx | 7 +++ src/components/MainLayout/index.tsx | 4 ++ src/components/UI/CRUD.tsx | 36 +++++++++++++++ src/components/UI/CRUDCard.tsx | 27 +++++++++++ src/pages/gym-locations/index.tsx | 24 ++++++++++ 6 files changed, 159 insertions(+) create mode 100644 src/components/GymLocations/index.tsx create mode 100644 src/components/UI/CRUD.tsx create mode 100644 src/components/UI/CRUDCard.tsx create mode 100644 src/pages/gym-locations/index.tsx diff --git a/src/components/GymLocations/index.tsx b/src/components/GymLocations/index.tsx new file mode 100644 index 0000000..31c675c --- /dev/null +++ b/src/components/GymLocations/index.tsx @@ -0,0 +1,61 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import React, { useState } from "react" +// types +import { GetGymLocationsOutput } from "src/types/trpc/router-types" +// components +import Text, { Typography } from "../UI/typography/Text" +import CRUD from "../UI/CRUD" +import AddGymLocationModal from "../CreateWorkout/AddGymLocationModal" +import Modal from "../UI/Modal" +import SecondaryButton from "../UI/SecondaryButton" +import CRUDCard from "../UI/CRUDCard" + +interface GymLocationsProps { + gymLocations: GetGymLocationsOutput | undefined +} + +export default function GymLocations({ gymLocations }: GymLocationsProps) { + const [isCreate, setIsCreate] = useState(false) + return ( + setIsCreate(true)} + h3Text="Gym Locations" + modals={ + <> + setIsCreate(false)} + cardTitle="Add Gym Location" + > + setIsCreate(false)} /> + + + } + secondaryCards={ + gymLocations && gymLocations.length > 0 ? ( + gymLocations.map(({ gymId, description, name }) => ( + console.log("clicked edit")} + onClickDelete={() => console.log("clicked delete")} + > +
+ + +
+
+ )) + ) : ( + setIsCreate(true)} + /> + ) + } + /> + ) +} diff --git a/src/components/MainLayout/TopNavbar.tsx b/src/components/MainLayout/TopNavbar.tsx index e7cbba1..ae6ef11 100644 --- a/src/components/MainLayout/TopNavbar.tsx +++ b/src/components/MainLayout/TopNavbar.tsx @@ -16,6 +16,7 @@ interface TopNavbarProps { onToggleMenu: () => void onClickExercises: () => void onClickRuns: () => void + onClickGymLocation: () => void isMenuOpen: boolean } @@ -27,6 +28,7 @@ export default function TopNavbar({ onToggleMenu, onClickExercises, onClickRuns, + onClickGymLocation, }: TopNavbarProps) { const anchorEl = useRef(null) const popupRef = useRef(null) @@ -80,6 +82,11 @@ export default function TopNavbar({ }, }} > + { + setIsMenuOpen(false) + router.push("/gym-locations") + }} />
diff --git a/src/components/UI/CRUD.tsx b/src/components/UI/CRUD.tsx new file mode 100644 index 0000000..638e56a --- /dev/null +++ b/src/components/UI/CRUD.tsx @@ -0,0 +1,36 @@ +import React from "react" +// components +import PrimaryButton from "./PrimaryButton" +import Text, { Typography } from "./typography/Text" +import ParentCard from "./ParentCard" + +interface CRUDProps { + modals: React.ReactNode + secondaryCards: React.ReactNode + onCreate: () => void // toggle a create modal + h3Text: string +} + +// CRUD layout styling +export default function CRUD({ + modals, + onCreate, + h3Text, + secondaryCards, +}: CRUDProps) { + return ( +
+ +
+ + {secondaryCards} +
+ {modals} +
+ ) +} diff --git a/src/components/UI/CRUDCard.tsx b/src/components/UI/CRUDCard.tsx new file mode 100644 index 0000000..f129f83 --- /dev/null +++ b/src/components/UI/CRUDCard.tsx @@ -0,0 +1,27 @@ +import React from "react" +// components +import SecondaryCard from "./SecondaryCard" +import EditIcon from "./icons/EditIcon" +import TrashIcon from "./icons/TrashIcon" + +interface CrudCardProps { + onClickEdit: () => void + onClickDelete: () => void + children: React.ReactNode +} + +export default function CRUDCard({ + children, + onClickEdit, + onClickDelete, +}: CrudCardProps) { + return ( + +
{children}
+
+ + +
+
+ ) +} diff --git a/src/pages/gym-locations/index.tsx b/src/pages/gym-locations/index.tsx new file mode 100644 index 0000000..94adb9f --- /dev/null +++ b/src/pages/gym-locations/index.tsx @@ -0,0 +1,24 @@ +import React from "react" +// hooks +import useGetGymLocations from "src/hooks/useGetGymLocations" +// components +import MainLayout from "src/components/MainLayout" +import GymLocationsContainer from "src/components/GymLocations" +import LoadingSpinner from "src/components/UI/LoadingSpinner" +import Fade from "src/components/UI/transitions/Fade" + +export default function GymLocations() { + const { data, isLoading } = useGetGymLocations() + + return isLoading ? ( + + ) : ( + + + + ) +} + +GymLocations.getLayout = function getLayout(page: React.ReactElement) { + return {page} +} From 77375cd4a4aa240c0df3e866a4093b6e402d62ee Mon Sep 17 00:00:00 2001 From: bcheung Date: Sat, 4 Nov 2023 22:23:27 -0400 Subject: [PATCH 14/19] adds delete warning for gym locations page --- .../migration.sql | 5 ++ prisma/schema.prisma | 2 +- src/components/GymLocations/index.tsx | 29 ++++++++++- src/components/UI/Warning.tsx | 49 +++++++++++++++++++ src/hooks/useMutationDeleteGymLocation.tsx | 15 ++++++ .../delete-gym-location.ts | 29 +++++++++++ src/server/routers/gym-location-router.ts | 2 + 7 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 prisma/migrations/20231105020921_add_cascade_for_delete/migration.sql create mode 100644 src/components/UI/Warning.tsx create mode 100644 src/hooks/useMutationDeleteGymLocation.tsx create mode 100644 src/server/procedures/gym-location-procedures/delete-gym-location.ts diff --git a/prisma/migrations/20231105020921_add_cascade_for_delete/migration.sql b/prisma/migrations/20231105020921_add_cascade_for_delete/migration.sql new file mode 100644 index 0000000..4825aca --- /dev/null +++ b/prisma/migrations/20231105020921_add_cascade_for_delete/migration.sql @@ -0,0 +1,5 @@ +-- DropForeignKey +ALTER TABLE "WorkoutPlan" DROP CONSTRAINT "WorkoutPlan_gymId_fkey"; + +-- AddForeignKey +ALTER TABLE "WorkoutPlan" ADD CONSTRAINT "WorkoutPlan_gymId_fkey" FOREIGN KEY ("gymId") REFERENCES "GymLocation"("gymId") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4be10a1..c3c76a1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -136,7 +136,7 @@ model WorkoutPlan { // relations user User @relation(fields: [userId], references: [userId]) userId String - gymLocation GymLocation @relation(fields: [gymId], references: [gymId]) + gymLocation GymLocation @relation(fields: [gymId], references: [gymId], onDelete: Cascade) gymId String @db.Uuid exercises Exercise[] diff --git a/src/components/GymLocations/index.tsx b/src/components/GymLocations/index.tsx index 31c675c..f67209c 100644 --- a/src/components/GymLocations/index.tsx +++ b/src/components/GymLocations/index.tsx @@ -2,6 +2,9 @@ import React, { useState } from "react" // types import { GetGymLocationsOutput } from "src/types/trpc/router-types" +// hooks +import useMutationDeleteGymLocation from "src/hooks/useMutationDeleteGymLocation" +import useToastMessage, { ToastMessage } from "src/hooks/useToastMessage" // components import Text, { Typography } from "../UI/typography/Text" import CRUD from "../UI/CRUD" @@ -9,13 +12,24 @@ import AddGymLocationModal from "../CreateWorkout/AddGymLocationModal" import Modal from "../UI/Modal" import SecondaryButton from "../UI/SecondaryButton" import CRUDCard from "../UI/CRUDCard" +import Warning from "../UI/Warning" interface GymLocationsProps { gymLocations: GetGymLocationsOutput | undefined } export default function GymLocations({ gymLocations }: GymLocationsProps) { + const [isWarning, setIsWarning] = useState(false) + const toastMessage = useToastMessage() + const { mutate } = useMutationDeleteGymLocation( + () => { + toastMessage("Gym location deleted successfully", ToastMessage.Success) + }, + () => + toastMessage("Err: Unable to delete gym location.", ToastMessage.Error) + ) const [isCreate, setIsCreate] = useState(false) + return ( setIsCreate(true)} @@ -37,7 +51,7 @@ export default function GymLocations({ gymLocations }: GymLocationsProps) { console.log("clicked edit")} - onClickDelete={() => console.log("clicked delete")} + onClickDelete={() => setIsWarning(true)} >
@@ -45,6 +59,19 @@ export default function GymLocations({ gymLocations }: GymLocationsProps) { text={description ? description : "no description"} typography={Typography.p3} /> + setIsWarning(false)} + warningMsg={ + "All workout plans associated with this gym location will be deleted." + } + onConfirm={() => { + mutate({ gymId }) + setIsWarning(false) + }} + onCancel={() => setIsWarning(false)} + warningTitle="You are about to delete a gym location." + />
)) diff --git a/src/components/UI/Warning.tsx b/src/components/UI/Warning.tsx new file mode 100644 index 0000000..d3916d0 --- /dev/null +++ b/src/components/UI/Warning.tsx @@ -0,0 +1,49 @@ +import Modal from "./Modal" +import PrimaryButton from "./PrimaryButton" +import SecondaryButton from "./SecondaryButton" +import Text from "./typography/Text" + +interface WarningProps { + isOpen: boolean + onCloseModal: () => void + warningMsg: string + onConfirm: () => void + onCancel: () => void + warningTitle: string +} + +export default function Warning({ + isOpen, + onCloseModal, + warningMsg, + onConfirm, + onCancel, + warningTitle, +}: WarningProps) { + return ( + +
+ + +
+ + +
+
+
+ ) +} diff --git a/src/hooks/useMutationDeleteGymLocation.tsx b/src/hooks/useMutationDeleteGymLocation.tsx new file mode 100644 index 0000000..20600fb --- /dev/null +++ b/src/hooks/useMutationDeleteGymLocation.tsx @@ -0,0 +1,15 @@ +import { trpc } from "src/utils/trpc" + +export default function useMutationDeleteGymLocation( + onSuccess: () => void, + onError: () => void +) { + const utils = trpc.useContext() + + return trpc.gymLocations.deleteGymLocation.useMutation({ + onSuccess: () => { + utils.gymLocations.invalidate() + }, + onError, + }) +} diff --git a/src/server/procedures/gym-location-procedures/delete-gym-location.ts b/src/server/procedures/gym-location-procedures/delete-gym-location.ts new file mode 100644 index 0000000..4c27aca --- /dev/null +++ b/src/server/procedures/gym-location-procedures/delete-gym-location.ts @@ -0,0 +1,29 @@ +import { TRPCError } from "@trpc/server" +import { tProtectedProcedure } from "src/server/trpc" +import prisma from "src/utils/prisma" +import { z } from "zod" + +const deleteGymLocation = tProtectedProcedure + .input( + z.object({ + gymId: z.string().min(1), + }) + ) + .mutation(async ({ input: { gymId } }) => { + try { + await prisma.gymLocation.delete({ + where: { + gymId, + }, + }) + return "Ok" + } catch (error) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Error deleting gym location.", + cause: error, + }) + } + }) + +export default deleteGymLocation diff --git a/src/server/routers/gym-location-router.ts b/src/server/routers/gym-location-router.ts index b5721f0..d5dcc57 100644 --- a/src/server/routers/gym-location-router.ts +++ b/src/server/routers/gym-location-router.ts @@ -2,10 +2,12 @@ import { trouter } from "../trpc" // procedures import addGymLocation from "../procedures/gym-location-procedures/add-gym-location" import getGymLocations from "../procedures/gym-location-procedures/get-gym-location" +import deleteGymLocation from "../procedures/gym-location-procedures/delete-gym-location" const gymLocationRouter = trouter({ addGymLocation, getGymLocations, + deleteGymLocation, }) export default gymLocationRouter From deec7164a6c251b329fbcb6dbcf03e85abc226f0 Mon Sep 17 00:00:00 2001 From: bcheung Date: Sat, 4 Nov 2023 22:26:13 -0400 Subject: [PATCH 15/19] adds layout for running page --- src/pages/running/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/running/index.tsx b/src/pages/running/index.tsx index a132607..4719159 100644 --- a/src/pages/running/index.tsx +++ b/src/pages/running/index.tsx @@ -1,5 +1,10 @@ -import React from "react" +import { ReactElement } from "react" +import MainLayout from "src/components/MainLayout" export default function RunningPage() { return
RunningPage
} + +RunningPage.getLayout = function getLayout(page: ReactElement) { + return {page} +} From d49f9176a509f740de81edacb7836be24985624a Mon Sep 17 00:00:00 2001 From: bcheung Date: Sun, 5 Nov 2023 07:52:07 -0500 Subject: [PATCH 16/19] add update gym location --- .../GymLocations/EditGymLocationModal.tsx | 70 +++++++++++++++++++ src/components/GymLocations/index.tsx | 20 +++++- src/components/UI/Warning.tsx | 18 +---- src/hooks/useGetGymLocationById.tsx | 5 ++ .../useMutationUpdateGymLocationById.tsx | 17 +++++ .../get-location-by-id.ts | 27 +++++++ .../update-gym-locaion-by-id.ts | 33 +++++++++ src/server/routers/gym-location-router.ts | 4 ++ src/validators/gym-location-schema.ts | 7 ++ 9 files changed, 184 insertions(+), 17 deletions(-) create mode 100644 src/components/GymLocations/EditGymLocationModal.tsx create mode 100644 src/hooks/useGetGymLocationById.tsx create mode 100644 src/hooks/useMutationUpdateGymLocationById.tsx create mode 100644 src/server/procedures/gym-location-procedures/get-location-by-id.ts create mode 100644 src/server/procedures/gym-location-procedures/update-gym-locaion-by-id.ts create mode 100644 src/validators/gym-location-schema.ts diff --git a/src/components/GymLocations/EditGymLocationModal.tsx b/src/components/GymLocations/EditGymLocationModal.tsx new file mode 100644 index 0000000..16889b9 --- /dev/null +++ b/src/components/GymLocations/EditGymLocationModal.tsx @@ -0,0 +1,70 @@ +import React, { useEffect } from "react" +import { z } from "zod" +import { useForm } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +// hooks +import useGetGymLocationById from "src/hooks/useGetGymLocationById" +import useMutationUpdateGymLocationById from "src/hooks/useMutationUpdateGymLocationById" +// components +import LoadingSpinner from "../UI/LoadingSpinner" +import FormInput from "../UI/FormInput" +import YesNoBtnGroup from "../UI/YesNoBtnGroup" +import { UpdateGymLocationSchema } from "src/validators/gym-location-schema" + +type UpdateGymLocationType = z.infer + +interface EditGymLocationModalProps { + editGymId: string + onClickDecline: () => void +} + +export default function EditGymLocationModal({ + editGymId, + onClickDecline, +}: EditGymLocationModalProps) { + const { data, isLoading } = useGetGymLocationById(editGymId) + const { mutate, isLoading: isMutating } = useMutationUpdateGymLocationById() + const { control, handleSubmit, setValue, reset } = + useForm({ + defaultValues: { + name: "", + description: "", + gymId: "", + }, + resolver: zodResolver(UpdateGymLocationSchema), + }) + + const handleSubmitForm = async (data: UpdateGymLocationType) => { + mutate(data) + reset() + onClickDecline() + } + + useEffect(() => { + if (data) { + setValue("description", data?.description ? data.description : "") + setValue("gymId", data.gymId) + setValue("name", data.name) + } + }, [data, setValue]) + + return isLoading ? ( + + ) : ( +
+ + + + + ) +} diff --git a/src/components/GymLocations/index.tsx b/src/components/GymLocations/index.tsx index f67209c..9d313ba 100644 --- a/src/components/GymLocations/index.tsx +++ b/src/components/GymLocations/index.tsx @@ -13,13 +13,16 @@ import Modal from "../UI/Modal" import SecondaryButton from "../UI/SecondaryButton" import CRUDCard from "../UI/CRUDCard" import Warning from "../UI/Warning" +import EditGymLocationModal from "./EditGymLocationModal" interface GymLocationsProps { gymLocations: GetGymLocationsOutput | undefined } export default function GymLocations({ gymLocations }: GymLocationsProps) { + const [editGymId, setEditGymId] = useState("") const [isWarning, setIsWarning] = useState(false) + const [isEdit, setIsEdit] = useState(false) const toastMessage = useToastMessage() const { mutate } = useMutationDeleteGymLocation( () => { @@ -43,6 +46,18 @@ export default function GymLocations({ gymLocations }: GymLocationsProps) { > setIsCreate(false)} /> + { + setIsEdit(false) + }} + cardTitle="Edit Gym Location" + > + setIsEdit(false)} + /> + } secondaryCards={ @@ -50,7 +65,10 @@ export default function GymLocations({ gymLocations }: GymLocationsProps) { gymLocations.map(({ gymId, description, name }) => ( console.log("clicked edit")} + onClickEdit={() => { + setIsEdit(true) + setEditGymId(gymId) + }} onClickDelete={() => setIsWarning(true)} >
diff --git a/src/components/UI/Warning.tsx b/src/components/UI/Warning.tsx index d3916d0..b5cca6c 100644 --- a/src/components/UI/Warning.tsx +++ b/src/components/UI/Warning.tsx @@ -1,6 +1,5 @@ import Modal from "./Modal" -import PrimaryButton from "./PrimaryButton" -import SecondaryButton from "./SecondaryButton" +import YesNoBtnGroup from "./YesNoBtnGroup" import Text from "./typography/Text" interface WarningProps { @@ -29,20 +28,7 @@ export default function Warning({
-
- - -
+
) diff --git a/src/hooks/useGetGymLocationById.tsx b/src/hooks/useGetGymLocationById.tsx new file mode 100644 index 0000000..8e2b59f --- /dev/null +++ b/src/hooks/useGetGymLocationById.tsx @@ -0,0 +1,5 @@ +import { trpc } from "src/utils/trpc" + +export default function useGetGymLocationById(gymId: string) { + return trpc.gymLocations.getLocationById.useQuery(gymId) +} diff --git a/src/hooks/useMutationUpdateGymLocationById.tsx b/src/hooks/useMutationUpdateGymLocationById.tsx new file mode 100644 index 0000000..543087f --- /dev/null +++ b/src/hooks/useMutationUpdateGymLocationById.tsx @@ -0,0 +1,17 @@ +import { trpc } from "src/utils/trpc" +import useToastMessage, { ToastMessage } from "./useToastMessage" + +export default function useMutationUpdateGymLocationById() { + const toastMessage = useToastMessage() + const util = trpc.useContext() + + return trpc.gymLocations.updateGymLocationById.useMutation({ + onSuccess: () => { + toastMessage("Successfully updated gym location.", ToastMessage.Success) + util.gymLocations.invalidate() + }, + onError: () => { + toastMessage("Err: Unable to update gym location.", ToastMessage.Error) + }, + }) +} diff --git a/src/server/procedures/gym-location-procedures/get-location-by-id.ts b/src/server/procedures/gym-location-procedures/get-location-by-id.ts new file mode 100644 index 0000000..6d48268 --- /dev/null +++ b/src/server/procedures/gym-location-procedures/get-location-by-id.ts @@ -0,0 +1,27 @@ +import { TRPCError } from "@trpc/server" +import { tProtectedProcedure } from "src/server/trpc" +import prisma from "src/utils/prisma" +import { z } from "zod" + +const getLocationById = tProtectedProcedure + .input(z.string().min(1)) + .query(async ({ input: gymId, ctx: { user } }) => { + try { + const gymLocation = await prisma.gymLocation.findUniqueOrThrow({ + where: { + gymId, + userId: user.id, + }, + }) + + return gymLocation + } catch (error) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Error getting single gym location.", + cause: error, + }) + } + }) + +export default getLocationById diff --git a/src/server/procedures/gym-location-procedures/update-gym-locaion-by-id.ts b/src/server/procedures/gym-location-procedures/update-gym-locaion-by-id.ts new file mode 100644 index 0000000..a722b26 --- /dev/null +++ b/src/server/procedures/gym-location-procedures/update-gym-locaion-by-id.ts @@ -0,0 +1,33 @@ +import { TRPCError } from "@trpc/server" +import { tProtectedProcedure } from "src/server/trpc" +import prisma from "src/utils/prisma" +// types +import { UpdateGymLocationSchema } from "src/validators/gym-location-schema" + +const updateGymLocationById = tProtectedProcedure + .input(UpdateGymLocationSchema) + .mutation(async ({ input: { description, gymId, name }, ctx: { user } }) => { + try { + const gymLocation = await prisma.gymLocation.update({ + where: { + gymId, + userId: user.id, + }, + data: { + description, + gymId, + name, + }, + }) + + return gymLocation + } catch (error) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Error updating gym location.", + cause: error, + }) + } + }) + +export default updateGymLocationById diff --git a/src/server/routers/gym-location-router.ts b/src/server/routers/gym-location-router.ts index d5dcc57..5710561 100644 --- a/src/server/routers/gym-location-router.ts +++ b/src/server/routers/gym-location-router.ts @@ -3,11 +3,15 @@ import { trouter } from "../trpc" import addGymLocation from "../procedures/gym-location-procedures/add-gym-location" import getGymLocations from "../procedures/gym-location-procedures/get-gym-location" import deleteGymLocation from "../procedures/gym-location-procedures/delete-gym-location" +import getLocationById from "../procedures/gym-location-procedures/get-location-by-id" +import updateGymLocationById from "../procedures/gym-location-procedures/update-gym-locaion-by-id" const gymLocationRouter = trouter({ addGymLocation, getGymLocations, deleteGymLocation, + getLocationById, + updateGymLocationById, }) export default gymLocationRouter diff --git a/src/validators/gym-location-schema.ts b/src/validators/gym-location-schema.ts new file mode 100644 index 0000000..f7115bd --- /dev/null +++ b/src/validators/gym-location-schema.ts @@ -0,0 +1,7 @@ +import { z } from "zod" + +export const UpdateGymLocationSchema = z.object({ + gymId: z.string().min(1), + name: z.string().min(1), + description: z.string().min(1), +}) From 3858a105cc469289cc19077bfecb5ca7ba3dcce6 Mon Sep 17 00:00:00 2001 From: bcheung Date: Sun, 5 Nov 2023 08:00:33 -0500 Subject: [PATCH 17/19] adds waiting before closing modal --- .../GymLocations/EditGymLocationModal.tsx | 9 ++++++--- src/components/GymLocations/index.tsx | 13 +++++++++--- src/components/UI/Warning.tsx | 20 ++++++++++++++----- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/components/GymLocations/EditGymLocationModal.tsx b/src/components/GymLocations/EditGymLocationModal.tsx index 16889b9..845cffa 100644 --- a/src/components/GymLocations/EditGymLocationModal.tsx +++ b/src/components/GymLocations/EditGymLocationModal.tsx @@ -35,9 +35,12 @@ export default function EditGymLocationModal({ }) const handleSubmitForm = async (data: UpdateGymLocationType) => { - mutate(data) - reset() - onClickDecline() + mutate(data, { + onSuccess: () => { + reset() + onClickDecline() + }, + }) } useEffect(() => { diff --git a/src/components/GymLocations/index.tsx b/src/components/GymLocations/index.tsx index 9d313ba..49a7482 100644 --- a/src/components/GymLocations/index.tsx +++ b/src/components/GymLocations/index.tsx @@ -24,7 +24,7 @@ export default function GymLocations({ gymLocations }: GymLocationsProps) { const [isWarning, setIsWarning] = useState(false) const [isEdit, setIsEdit] = useState(false) const toastMessage = useToastMessage() - const { mutate } = useMutationDeleteGymLocation( + const { mutate, isLoading: isDeleting } = useMutationDeleteGymLocation( () => { toastMessage("Gym location deleted successfully", ToastMessage.Success) }, @@ -78,14 +78,21 @@ export default function GymLocations({ gymLocations }: GymLocationsProps) { typography={Typography.p3} /> setIsWarning(false)} warningMsg={ "All workout plans associated with this gym location will be deleted." } onConfirm={() => { - mutate({ gymId }) - setIsWarning(false) + mutate( + { gymId }, + { + onSuccess: () => { + setIsWarning(false) + }, + } + ) }} onCancel={() => setIsWarning(false)} warningTitle="You are about to delete a gym location." diff --git a/src/components/UI/Warning.tsx b/src/components/UI/Warning.tsx index b5cca6c..6ec99c9 100644 --- a/src/components/UI/Warning.tsx +++ b/src/components/UI/Warning.tsx @@ -9,6 +9,7 @@ interface WarningProps { onConfirm: () => void onCancel: () => void warningTitle: string + isLoading?: boolean } export default function Warning({ @@ -18,6 +19,7 @@ export default function Warning({ onConfirm, onCancel, warningTitle, + isLoading, }: WarningProps) { return ( -
- - - -
+ {isLoading ? ( +
Loading...
+ ) : ( +
+ + + +
+ )}
) } + +Warning.defaultProps = { + isLoading: false, +} From ea71d2f84ebe5eccea3695ca82f61e1585a25324 Mon Sep 17 00:00:00 2001 From: bcheung Date: Sun, 5 Nov 2023 08:19:52 -0500 Subject: [PATCH 18/19] adds dropdown filter by gymlocation for workout plan --- src/components/UI/FormSelect.tsx | 5 +--- src/components/UI/SelectDropdown.tsx | 29 ++++++++++++++++++++++ src/components/Workouts/index.tsx | 37 +++++++++++++++++++++------- 3 files changed, 58 insertions(+), 13 deletions(-) create mode 100644 src/components/UI/SelectDropdown.tsx diff --git a/src/components/UI/FormSelect.tsx b/src/components/UI/FormSelect.tsx index 02d935b..4b7b0c9 100644 --- a/src/components/UI/FormSelect.tsx +++ b/src/components/UI/FormSelect.tsx @@ -43,10 +43,7 @@ export default function FormSelect({ ) : ( - <> - - - + )} {error &&

{error.message}

} diff --git a/src/components/UI/SelectDropdown.tsx b/src/components/UI/SelectDropdown.tsx new file mode 100644 index 0000000..7e21a88 --- /dev/null +++ b/src/components/UI/SelectDropdown.tsx @@ -0,0 +1,29 @@ +import React from "react" +import { MenuOption } from "src/types/menu" + +interface SelectDropdownProps { + menuList: MenuOption[] + value: string | number + onChange: React.ChangeEventHandler +} + +export default function SelectDropdown({ + menuList, + onChange, + value, +}: SelectDropdownProps) { + return ( + + ) +} diff --git a/src/components/Workouts/index.tsx b/src/components/Workouts/index.tsx index 8a8c8ab..9871458 100644 --- a/src/components/Workouts/index.tsx +++ b/src/components/Workouts/index.tsx @@ -18,12 +18,16 @@ import TrashIcon from "../UI/icons/TrashIcon" import Modal from "../UI/Modal" import EditWorkoutPlan from "./EditWorkoutPlan" import YesNoBtnGroup from "../UI/YesNoBtnGroup" +import SelectDropdown from "../UI/SelectDropdown" interface WorkoutsProps { plans: GetWorkoutPlansOutput | undefined } export default function Workouts({ plans }: WorkoutsProps) { + const [selectedGymLocation, setSelectedGymLocation] = useState( + (plans && plans[0].planId) || "" + ) const [selectedPlanId, setSelectedPlanId] = useState("") const [isEdit, setIsEdit] = useState(false) const [isConfirmDelete, setIsConfirmDelete] = useState(false) @@ -52,6 +56,7 @@ export default function Workouts({ plans }: WorkoutsProps) { const handleCloseEdit = () => setIsEdit(false) const handleDeleteWorkout = (planId: string) => mutate(planId) + return (
- +
+ + setSelectedGymLocation(e.target.value)} + menuList={ + plans?.map((plan) => ({ + id: plan.gymId, + name: plan.name, + value: plan.gymId, + })) || [] + } + /> +
{plans && plans.length > 0 ? ( - plans.map( - ({ planId, name, lastWorkout, duration, gymLocation }) => ( + // upgrade to reducer fn? + plans + .filter((plan) => plan.gymId === selectedGymLocation) + .map(({ planId, name, lastWorkout, duration, gymLocation }) => (
- ) - ) + )) ) : ( router.push("/workouts/create-workout")} From 1538eae97d42bac4681a6c722faf978692cfeb64 Mon Sep 17 00:00:00 2001 From: bcheung Date: Sun, 5 Nov 2023 08:42:10 -0500 Subject: [PATCH 19/19] update mocks, remove test --- src/__tests__/jest/Workouts.test.tsx | 30 --- src/components/UI/SelectDropdown.tsx | 2 +- src/components/Workouts/index.tsx | 160 ++++++------ src/mocks/workouts.ts | 359 ++++++++++++++++++++++++--- src/pages/workouts/index.tsx | 10 +- 5 files changed, 418 insertions(+), 143 deletions(-) delete mode 100644 src/__tests__/jest/Workouts.test.tsx diff --git a/src/__tests__/jest/Workouts.test.tsx b/src/__tests__/jest/Workouts.test.tsx deleted file mode 100644 index 9ce7213..0000000 --- a/src/__tests__/jest/Workouts.test.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import "@testing-library/jest-dom" -import { render, screen } from "@testing-library/react" -// components -import Workouts from "src/components/Workouts" -// mocks -import { MOCK_WORKOUT_PLANS } from "src/mocks/workouts" - -jest.mock("next/router", () => require("next-router-mock")) - -describe("test Workouts Page", () => { - it("should render 'get started'", async () => { - render() - expect( - screen.getByRole("button", { name: /Get Started/i }) - ).toBeInTheDocument() - }) - - it("should render three workout templates", async () => { - render() - expect( - screen.queryByRole("button", { name: /Get Started/i }) - ).not.toBeInTheDocument() - - for (let i = 0; i < MOCK_WORKOUT_PLANS.length; i++) { - const string = `workout plan ${i + 1}` - const title = screen.getByText(new RegExp(string, "i")) - expect(title).toBeInTheDocument() - } - }) -}) diff --git a/src/components/UI/SelectDropdown.tsx b/src/components/UI/SelectDropdown.tsx index 7e21a88..3b671a5 100644 --- a/src/components/UI/SelectDropdown.tsx +++ b/src/components/UI/SelectDropdown.tsx @@ -20,7 +20,7 @@ export default function SelectDropdown({ > {menuList.map((option) => ( - ))} diff --git a/src/components/Workouts/index.tsx b/src/components/Workouts/index.tsx index 9871458..79afe5a 100644 --- a/src/components/Workouts/index.tsx +++ b/src/components/Workouts/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo } from "react" +import React, { useState, useMemo, useEffect } from "react" import { useRouter } from "next/router" import dayjs from "dayjs" // hooks @@ -6,7 +6,10 @@ import useToastMessage, { ToastMessage } from "src/hooks/useToastMessage" import useMutationDeleteWorkoutPlan from "src/hooks/useMutationDeleteWorkoutPlan" // types/utils import MsToStrTime from "src/utils/MsToStrTime" -import { GetWorkoutPlansOutput } from "src/types/trpc/router-types" +import { + GetGymLocationsOutput, + GetWorkoutPlansOutput, +} from "src/types/trpc/router-types" // components import PrimaryButton from "../UI/PrimaryButton" import Text, { Typography } from "../UI/typography/Text" @@ -22,9 +25,10 @@ import SelectDropdown from "../UI/SelectDropdown" interface WorkoutsProps { plans: GetWorkoutPlansOutput | undefined + gymLocations: GetGymLocationsOutput | undefined } -export default function Workouts({ plans }: WorkoutsProps) { +export default function Workouts({ plans, gymLocations }: WorkoutsProps) { const [selectedGymLocation, setSelectedGymLocation] = useState( (plans && plans[0].planId) || "" ) @@ -57,6 +61,81 @@ export default function Workouts({ plans }: WorkoutsProps) { const handleDeleteWorkout = (planId: string) => mutate(planId) + // upgrade to reducer fn? + const filteredPlans = + (plans && + plans.length > 0 && + plans + .filter((plan) => plan.gymId === selectedGymLocation) + .map(({ planId, name, lastWorkout, duration, gymLocation }) => ( + +
+ +
+ + + +
+
+
+ { + setSelectedPlanId(planId) + setIsEdit(true) + }} + /> + { + setSelectedPlanId(planId) + setIsConfirmDelete(true) + }} + /> +
+
+ ))) || + [] + + useEffect(() => { + if (plans && plans.length > 0) { + setSelectedGymLocation(plans[0].gymId) + } + }, [plans]) + return (
setSelectedGymLocation(e.target.value)} menuList={ - plans?.map((plan) => ({ - id: plan.gymId, - name: plan.name, - value: plan.gymId, + gymLocations?.map((gymLocation) => ({ + id: gymLocation.gymId, + name: gymLocation.name, + value: gymLocation.gymId, })) || [] } />
- {plans && plans.length > 0 ? ( - // upgrade to reducer fn? - plans - .filter((plan) => plan.gymId === selectedGymLocation) - .map(({ planId, name, lastWorkout, duration, gymLocation }) => ( - -
- -
- - - -
-
-
- { - setSelectedPlanId(planId) - setIsEdit(true) - }} - /> - { - setSelectedPlanId(planId) - setIsConfirmDelete(true) - }} - /> -
-
- )) + {filteredPlans && filteredPlans.length > 0 ? ( + filteredPlans ) : ( router.push("/workouts/create-workout")} diff --git a/src/mocks/workouts.ts b/src/mocks/workouts.ts index 0b0a0f7..4d5285d 100644 --- a/src/mocks/workouts.ts +++ b/src/mocks/workouts.ts @@ -2,73 +2,356 @@ import { GetWorkoutPlansOutput } from "src/types/trpc/router-types" export const MOCK_WORKOUT_PLANS: GetWorkoutPlansOutput = [ { - planId: "e1c88154-a47f-4c88-b50d-76e3e805c08c", - updatedAt: "2023-07-03T22:39:13.114Z", - createdAt: "2023-07-03T22:39:13.114Z", - name: "test123", + planId: "25eb6ffa-29a9-47cd-a43c-aac6877d1006", + updatedAt: "2023-11-05T13:03:22.606Z", + createdAt: "2023-11-05T13:03:22.606Z", + name: "push", exerciseOrder: [ - "b9ab3aa5-850e-4f32-a910-3cc4514b8eee", - "ebf517b4-fd5d-4e90-95bd-e6d458666547", - "c2260d6d-fd4e-4cbd-b006-5a414b8d9e7a", + "b578dfba-2599-4484-a72a-c7c37a9f0664", + "3142047d-943b-4161-855e-18d359d22c62", + "0070be4a-3821-403c-a6b1-0ad803386d13", + "22b98b2a-a176-4bc1-aa92-b6a911ef0f20", + "78c1392b-304d-4f4c-ace3-d3b5a94e25ae", + "5fbea3e6-e1aa-4193-9226-dbd1573b8ea0", ], lastWorkout: null, duration: null, - userId: "bbab5589-4955-4a45-b2b1-c17ee04b3c0f", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + gymId: "66bafe3e-1051-4331-8a4a-e6d18e82cdab", targets: [ { - targetId: "eadddb82-5be6-4ab7-b9fd-cb51a8fa9c3e", - updatedAt: "2023-07-03T22:39:13.427Z", - createdAt: "2023-07-03T22:39:13.427Z", + targetId: "f3d9d428-ab55-41e8-bb4e-a9853d9200f5", + updatedAt: "2023-11-05T13:03:22.974Z", + createdAt: "2023-11-05T13:03:22.974Z", targetReps: 8, targetSets: 3, - exerciseId: "ebf517b4-fd5d-4e90-95bd-e6d458666547", - planId: "e1c88154-a47f-4c88-b50d-76e3e805c08c", + exerciseId: "b578dfba-2599-4484-a72a-c7c37a9f0664", + planId: "25eb6ffa-29a9-47cd-a43c-aac6877d1006", exercise: { - exerciseId: "ebf517b4-fd5d-4e90-95bd-e6d458666547", - updatedAt: "2023-07-03T01:59:45.088Z", - createdAt: "2023-07-03T01:59:45.088Z", - name: "bench", + exerciseId: "b578dfba-2599-4484-a72a-c7c37a9f0664", + updatedAt: "2023-11-05T13:01:57.010Z", + createdAt: "2023-11-05T13:01:57.010Z", + name: "DB Bench Press", description: "", url: "", - userId: "bbab5589-4955-4a45-b2b1-c17ee04b3c0f", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", }, }, { - targetId: "5e91d718-ecf5-4d08-aea6-81ea0c39e7fd", - updatedAt: "2023-07-03T22:39:13.427Z", - createdAt: "2023-07-03T22:39:13.427Z", + targetId: "fdb7ee2d-82de-452f-81b6-04a2158667fa", + updatedAt: "2023-11-05T13:03:22.974Z", + createdAt: "2023-11-05T13:03:22.974Z", targetReps: 8, targetSets: 3, - exerciseId: "b9ab3aa5-850e-4f32-a910-3cc4514b8eee", - planId: "e1c88154-a47f-4c88-b50d-76e3e805c08c", + exerciseId: "3142047d-943b-4161-855e-18d359d22c62", + planId: "25eb6ffa-29a9-47cd-a43c-aac6877d1006", exercise: { - exerciseId: "b9ab3aa5-850e-4f32-a910-3cc4514b8eee", - updatedAt: "2023-07-03T02:00:03.085Z", - createdAt: "2023-07-03T02:00:03.085Z", - name: "backrow", + exerciseId: "3142047d-943b-4161-855e-18d359d22c62", + updatedAt: "2023-11-05T13:02:19.881Z", + createdAt: "2023-11-05T13:02:19.881Z", + name: "Lat shoulder raise", description: "", url: "", - userId: "bbab5589-4955-4a45-b2b1-c17ee04b3c0f", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", }, }, { - targetId: "67dc673d-be36-43a1-b85c-20e1322a1a2a", - updatedAt: "2023-07-03T22:39:13.427Z", - createdAt: "2023-07-03T22:39:13.427Z", + targetId: "9006739b-83d9-4345-83c7-fad269ea3d63", + updatedAt: "2023-11-05T13:03:22.974Z", + createdAt: "2023-11-05T13:03:22.974Z", targetReps: 8, targetSets: 3, - exerciseId: "c2260d6d-fd4e-4cbd-b006-5a414b8d9e7a", - planId: "e1c88154-a47f-4c88-b50d-76e3e805c08c", + exerciseId: "78c1392b-304d-4f4c-ace3-d3b5a94e25ae", + planId: "25eb6ffa-29a9-47cd-a43c-aac6877d1006", exercise: { - exerciseId: "c2260d6d-fd4e-4cbd-b006-5a414b8d9e7a", - updatedAt: "2023-07-03T01:59:59.980Z", - createdAt: "2023-07-03T01:59:59.980Z", - name: "deadlift", + exerciseId: "78c1392b-304d-4f4c-ace3-d3b5a94e25ae", + updatedAt: "2023-11-05T13:02:35.456Z", + createdAt: "2023-11-05T13:02:35.456Z", + name: "Machine Tricep bar pushdown", description: "", url: "", - userId: "bbab5589-4955-4a45-b2b1-c17ee04b3c0f", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + }, + }, + { + targetId: "c7f90ccc-9c16-4aa2-9126-f6bbdaa06a82", + updatedAt: "2023-11-05T13:03:22.974Z", + createdAt: "2023-11-05T13:03:22.974Z", + targetReps: -18, + targetSets: 3, + exerciseId: "22b98b2a-a176-4bc1-aa92-b6a911ef0f20", + planId: "25eb6ffa-29a9-47cd-a43c-aac6877d1006", + exercise: { + exerciseId: "22b98b2a-a176-4bc1-aa92-b6a911ef0f20", + updatedAt: "2023-11-05T13:02:26.688Z", + createdAt: "2023-11-05T13:02:26.688Z", + name: "Machine Shoulder Press", + description: "", + url: "", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + }, + }, + { + targetId: "70018b26-9ce3-4424-aa3c-64d98d85ef55", + updatedAt: "2023-11-05T13:03:22.974Z", + createdAt: "2023-11-05T13:03:22.974Z", + targetReps: 8, + targetSets: 3, + exerciseId: "0070be4a-3821-403c-a6b1-0ad803386d13", + planId: "25eb6ffa-29a9-47cd-a43c-aac6877d1006", + exercise: { + exerciseId: "0070be4a-3821-403c-a6b1-0ad803386d13", + updatedAt: "2023-11-05T13:01:50.550Z", + createdAt: "2023-11-05T13:01:50.550Z", + name: "Machine Bench Press", + description: "", + url: "", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + }, + }, + { + targetId: "92cf81db-ae4b-48a7-bf35-a718a576d627", + updatedAt: "2023-11-05T13:03:22.974Z", + createdAt: "2023-11-05T13:03:22.974Z", + targetReps: 8, + targetSets: 3, + exerciseId: "5fbea3e6-e1aa-4193-9226-dbd1573b8ea0", + planId: "25eb6ffa-29a9-47cd-a43c-aac6877d1006", + exercise: { + exerciseId: "5fbea3e6-e1aa-4193-9226-dbd1573b8ea0", + updatedAt: "2023-11-05T13:02:48.494Z", + createdAt: "2023-11-05T13:02:48.494Z", + name: "Tricep single arm db raise", + description: "", + url: "", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + }, + }, + ], + gymLocation: { + name: "apt gym", + }, + }, + { + planId: "7dabc5fe-b05a-410f-818b-e6a5933622c7", + updatedAt: "2023-11-05T13:04:05.643Z", + createdAt: "2023-11-05T13:04:05.643Z", + name: "option 2 location", + exerciseOrder: [ + "b578dfba-2599-4484-a72a-c7c37a9f0664", + "3142047d-943b-4161-855e-18d359d22c62", + "0070be4a-3821-403c-a6b1-0ad803386d13", + ], + lastWorkout: null, + duration: null, + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + gymId: "61e2c21b-8fea-47bd-8fac-1fac17c628fa", + targets: [ + { + targetId: "0e973b7a-a869-426d-96e8-ea96fb7917ee", + updatedAt: "2023-11-05T13:04:05.984Z", + createdAt: "2023-11-05T13:04:05.984Z", + targetReps: 8, + targetSets: 3, + exerciseId: "b578dfba-2599-4484-a72a-c7c37a9f0664", + planId: "7dabc5fe-b05a-410f-818b-e6a5933622c7", + exercise: { + exerciseId: "b578dfba-2599-4484-a72a-c7c37a9f0664", + updatedAt: "2023-11-05T13:01:57.010Z", + createdAt: "2023-11-05T13:01:57.010Z", + name: "DB Bench Press", + description: "", + url: "", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + }, + }, + { + targetId: "81974585-7adb-4461-919e-db995cee7998", + updatedAt: "2023-11-05T13:04:05.984Z", + createdAt: "2023-11-05T13:04:05.984Z", + targetReps: 8, + targetSets: 3, + exerciseId: "3142047d-943b-4161-855e-18d359d22c62", + planId: "7dabc5fe-b05a-410f-818b-e6a5933622c7", + exercise: { + exerciseId: "3142047d-943b-4161-855e-18d359d22c62", + updatedAt: "2023-11-05T13:02:19.881Z", + createdAt: "2023-11-05T13:02:19.881Z", + name: "Lat shoulder raise", + description: "", + url: "", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + }, + }, + { + targetId: "fb44e846-efa6-4be1-8283-3f1eb709c967", + updatedAt: "2023-11-05T13:04:05.984Z", + createdAt: "2023-11-05T13:04:05.984Z", + targetReps: 8, + targetSets: 3, + exerciseId: "0070be4a-3821-403c-a6b1-0ad803386d13", + planId: "7dabc5fe-b05a-410f-818b-e6a5933622c7", + exercise: { + exerciseId: "0070be4a-3821-403c-a6b1-0ad803386d13", + updatedAt: "2023-11-05T13:01:50.550Z", + createdAt: "2023-11-05T13:01:50.550Z", + name: "Machine Bench Press", + description: "", + url: "", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + }, + }, + ], + gymLocation: { + name: "option 2", + }, + }, + { + planId: "08c45387-1f30-4d4d-82c4-3fd8d746ec06", + updatedAt: "2023-11-05T13:34:53.363Z", + createdAt: "2023-11-05T13:34:53.363Z", + name: "test2", + exerciseOrder: [ + "b578dfba-2599-4484-a72a-c7c37a9f0664", + "3142047d-943b-4161-855e-18d359d22c62", + "0070be4a-3821-403c-a6b1-0ad803386d13", + ], + lastWorkout: null, + duration: null, + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + gymId: "66bafe3e-1051-4331-8a4a-e6d18e82cdab", + targets: [ + { + targetId: "3cbe96a8-3aa9-4637-bbb4-067f232b2a95", + updatedAt: "2023-11-05T13:34:53.703Z", + createdAt: "2023-11-05T13:34:53.703Z", + targetReps: 3, + targetSets: 3, + exerciseId: "3142047d-943b-4161-855e-18d359d22c62", + planId: "08c45387-1f30-4d4d-82c4-3fd8d746ec06", + exercise: { + exerciseId: "3142047d-943b-4161-855e-18d359d22c62", + updatedAt: "2023-11-05T13:02:19.881Z", + createdAt: "2023-11-05T13:02:19.881Z", + name: "Lat shoulder raise", + description: "", + url: "", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + }, + }, + { + targetId: "3934c2c9-1575-4c7b-92c8-be7125c4baa2", + updatedAt: "2023-11-05T13:34:53.703Z", + createdAt: "2023-11-05T13:34:53.703Z", + targetReps: 3, + targetSets: 3, + exerciseId: "b578dfba-2599-4484-a72a-c7c37a9f0664", + planId: "08c45387-1f30-4d4d-82c4-3fd8d746ec06", + exercise: { + exerciseId: "b578dfba-2599-4484-a72a-c7c37a9f0664", + updatedAt: "2023-11-05T13:01:57.010Z", + createdAt: "2023-11-05T13:01:57.010Z", + name: "DB Bench Press", + description: "", + url: "", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + }, + }, + { + targetId: "5f94a2e1-76ba-4f03-b149-7dc10d4956ba", + updatedAt: "2023-11-05T13:34:53.703Z", + createdAt: "2023-11-05T13:34:53.703Z", + targetReps: 3, + targetSets: 3, + exerciseId: "0070be4a-3821-403c-a6b1-0ad803386d13", + planId: "08c45387-1f30-4d4d-82c4-3fd8d746ec06", + exercise: { + exerciseId: "0070be4a-3821-403c-a6b1-0ad803386d13", + updatedAt: "2023-11-05T13:01:50.550Z", + createdAt: "2023-11-05T13:01:50.550Z", + name: "Machine Bench Press", + description: "", + url: "", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + }, + }, + ], + gymLocation: { + name: "apt gym", + }, + }, + { + planId: "3a615af4-0ead-4637-bd6a-c48f0c292756", + updatedAt: "2023-11-05T13:35:10.401Z", + createdAt: "2023-11-05T13:35:10.401Z", + name: "test 3", + exerciseOrder: [ + "22b98b2a-a176-4bc1-aa92-b6a911ef0f20", + "78c1392b-304d-4f4c-ace3-d3b5a94e25ae", + "5fbea3e6-e1aa-4193-9226-dbd1573b8ea0", + ], + lastWorkout: null, + duration: null, + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + gymId: "66bafe3e-1051-4331-8a4a-e6d18e82cdab", + targets: [ + { + targetId: "ffd371ab-7d0d-402d-a53d-15ce481bf58e", + updatedAt: "2023-11-05T13:35:10.651Z", + createdAt: "2023-11-05T13:35:10.651Z", + targetReps: 3, + targetSets: 3, + exerciseId: "22b98b2a-a176-4bc1-aa92-b6a911ef0f20", + planId: "3a615af4-0ead-4637-bd6a-c48f0c292756", + exercise: { + exerciseId: "22b98b2a-a176-4bc1-aa92-b6a911ef0f20", + updatedAt: "2023-11-05T13:02:26.688Z", + createdAt: "2023-11-05T13:02:26.688Z", + name: "Machine Shoulder Press", + description: "", + url: "", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + }, + }, + { + targetId: "ae918479-8aba-44eb-b8d4-1631dd538b30", + updatedAt: "2023-11-05T13:35:10.652Z", + createdAt: "2023-11-05T13:35:10.652Z", + targetReps: 3, + targetSets: 3, + exerciseId: "78c1392b-304d-4f4c-ace3-d3b5a94e25ae", + planId: "3a615af4-0ead-4637-bd6a-c48f0c292756", + exercise: { + exerciseId: "78c1392b-304d-4f4c-ace3-d3b5a94e25ae", + updatedAt: "2023-11-05T13:02:35.456Z", + createdAt: "2023-11-05T13:02:35.456Z", + name: "Machine Tricep bar pushdown", + description: "", + url: "", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", + }, + }, + { + targetId: "f9a3bbbb-928a-4ca6-bf05-9fdbdf8ca7bb", + updatedAt: "2023-11-05T13:35:10.652Z", + createdAt: "2023-11-05T13:35:10.652Z", + targetReps: 3, + targetSets: 13, + exerciseId: "5fbea3e6-e1aa-4193-9226-dbd1573b8ea0", + planId: "3a615af4-0ead-4637-bd6a-c48f0c292756", + exercise: { + exerciseId: "5fbea3e6-e1aa-4193-9226-dbd1573b8ea0", + updatedAt: "2023-11-05T13:02:48.494Z", + createdAt: "2023-11-05T13:02:48.494Z", + name: "Tricep single arm db raise", + description: "", + url: "", + userId: "ab270ab1-4350-4457-8bde-7fa17ce7b8b7", }, }, ], + gymLocation: { + name: "apt gym", + }, }, ] diff --git a/src/pages/workouts/index.tsx b/src/pages/workouts/index.tsx index 0a91ce0..0685431 100644 --- a/src/pages/workouts/index.tsx +++ b/src/pages/workouts/index.tsx @@ -6,14 +6,20 @@ import Workouts from "src/components/Workouts" import Fade from "src/components/UI/transitions/Fade" // hooks import useGetWorkoutPlans from "src/hooks/useGetWorkoutPlans" +import useGetGymLocations from "src/hooks/useGetGymLocations" export default function WorkoutsPage() { - const { data: plans, isLoading } = useGetWorkoutPlans() + const { data: plans, isLoading: isLoadingWorkoutPlans } = useGetWorkoutPlans() + const { data: gymLocations, isLoading: isLoadingGymLocations } = + useGetGymLocations() + + const isLoading = isLoadingGymLocations || isLoadingWorkoutPlans + return isLoading ? ( ) : ( - + ) }