diff --git a/cypress/e2e/authentication/sign-in.cy.js b/cypress/e2e/authentication/sign-in.cy.js index a7aef5a15..44d76eea3 100644 --- a/cypress/e2e/authentication/sign-in.cy.js +++ b/cypress/e2e/authentication/sign-in.cy.js @@ -39,7 +39,6 @@ describe("sign in flow", () => { cy.contains("button", "Sign In").should("be.visible"); cy.contains("button", "Sign In").click(); - cy.contains("Submission Error").should("be.visible"); }); }); diff --git a/package.json b/package.json index 6f3ccabd4..6acaebc7a 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@heroicons/react": "^2.0.18", "@hookform/resolvers": "^3.3.1", "@reduxjs/toolkit": "^1.9.5", + "@tanstack/react-query": "^5.59.20", "axios": "^1.7.7", "class-variance-authority": "^0.7.0", "date-fns": "^3.6.0", @@ -50,6 +51,8 @@ "@storybook/nextjs": "^7.6.4", "@storybook/react": "^7.6.4", "@storybook/test": "^7.6.4", + "@tanstack/eslint-plugin-query": "^5.59.20", + "@tanstack/react-query-devtools": "^5.59.20", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^14.0.0", "@types/node": "20.4.5", diff --git a/src/app/(auth)/sign-in/components/SignInFormContainer.tsx b/src/app/(auth)/sign-in/components/SignInFormContainer.tsx index ec419d5b1..d6145182f 100644 --- a/src/app/(auth)/sign-in/components/SignInFormContainer.tsx +++ b/src/app/(auth)/sign-in/components/SignInFormContainer.tsx @@ -3,6 +3,7 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { type SubmitHandler, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation } from "@tanstack/react-query"; import Button from "@/components/Button"; import TextInput from "@/components/inputs/TextInput"; import { validateTextInput } from "@/utils/form/validateInput"; @@ -12,6 +13,10 @@ import routePaths from "@/utils/routePaths"; import { type AuthClientAdapter } from "@/modules/auth/adapters/primary/authClientAdapter"; import { TYPES } from "@/di/types"; import { resolve } from "@/di/resolver"; +import type { LoginRequestDto } from "@/modules/auth/application/dtos/request.dto"; +import type { LoginResponseDto } from "@/modules/auth/application/dtos/response.dto"; +import Spinner from "@/components/Spinner"; +import { onOpenModal } from "@/store/features/modal/modalSlice"; const validationSchema = z.object({ email: validateTextInput({ @@ -39,28 +44,48 @@ function SignInFormContainer({ const router = useRouter(); const dispatch = useAppDispatch(); + const { mutate, isPending } = useMutation< + LoginResponseDto, + Error, + LoginRequestDto + >({ + mutationFn: loginMutation, + onSuccess: () => { + dispatch(clientSignIn()); + router.replace(routePaths.dashboardPage()); + }, + // TODO: update error handling + onError: (error: Error) => { + dispatch( + onOpenModal({ type: "error", content: { message: error.message } }), + ); + }, + }); + const { register, - formState: { errors }, + formState: { errors, isDirty, isValid }, handleSubmit, } = useForm({ mode: "onTouched", resolver: zodResolver(validationSchema), }); - // TODO: update error handling - const onSubmit: SubmitHandler = async (data) => { - const { email, password } = data; + async function loginMutation({ + email, + password, + }: LoginRequestDto): Promise { const authAdapter = resolve(TYPES.AuthClientAdapter); + return await authAdapter.login({ email, password }); + } - await authAdapter.login({ email, password }); - - dispatch(clientSignIn()); - router.replace(routePaths.dashboardPage()); + const onSubmit: SubmitHandler = (data) => { + const { email, password } = data; + mutate({ email, password }); }; function renderButtonContent() { - return "Sign In"; + return isPending ? : "Sign In"; } return ( @@ -95,7 +120,7 @@ function SignInFormContainer({ diff --git a/src/app/layout.tsx b/src/app/layout.tsx index eeb79365b..ea3a293e9 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -6,6 +6,7 @@ import { Inter } from "next/font/google"; import StoreProvider from "@/components/providers/StoreProvider"; import ThemeProvider from "@/components/providers/ThemeProvider"; import ModalProvider from "@/components/providers/ModalProvider"; +import { TanstackQueryProvider } from "@/components/providers/TanstackQueryProvider"; export const metadata: Metadata = { title: "Chingu Dashboard", @@ -33,8 +34,10 @@ export default function RootLayout({ - - {children} + + + {children} + diff --git a/src/components/navbar/DropDown.tsx b/src/components/navbar/DropDown.tsx index 6ecbed87c..efabe00d2 100644 --- a/src/components/navbar/DropDown.tsx +++ b/src/components/navbar/DropDown.tsx @@ -4,6 +4,7 @@ import "reflect-metadata"; import { ChevronDownIcon } from "@heroicons/react/24/outline"; import Link from "next/link"; import { useRouter } from "next/navigation"; +import { useMutation } from "@tanstack/react-query"; import Button from "@/components/Button"; import { useAppDispatch, useUser } from "@/store/hooks"; import { clientSignOut } from "@/store/features/auth/authSlice"; @@ -11,8 +12,14 @@ import { TYPES } from "@/di/types"; import { resolve } from "@/di/resolver"; import { type AuthClientAdapter } from "@/modules/auth/adapters/primary/authClientAdapter"; import routePaths from "@/utils/routePaths"; +import type { LogoutResponseDto } from "@/modules/auth/application/dtos/response.dto"; +import { onOpenModal } from "@/store/features/modal/modalSlice"; -export default function DropDown({ openState }: { openState?: boolean }) { +interface DropdownProps { + openState?: boolean; +} + +export default function DropDown({ openState }: DropdownProps) { const router = useRouter(); const dispatch = useAppDispatch(); const allVoyages = useUser().voyageTeamMembers; @@ -36,24 +43,29 @@ export default function DropDown({ openState }: { openState?: boolean }) { const open = "absolute flex flex-col gap-5 z-[1] w-[250px] p-5 bottom-100 translate-y-[15%] shadow-md bg-base-200 right-0 border border-base-100 rounded-2xl"; - // TODO: update error handling - async function handleClick() { - const authAdapter = resolve(TYPES.AuthClientAdapter); - - await authAdapter.logout(); - dispatch(clientSignOut()); - router.replace(routePaths.signIn()); - // if (res) { - // dispatch(clientSignOut()); - // } + function handleClick() { + mutate(); + } - // if (error) { - // dispatch( - // onOpenModal({ type: "error", content: { message: error.message } }), - // ); - // } + async function logoutMutation(): Promise { + const authAdapter = resolve(TYPES.AuthClientAdapter); + return await authAdapter.logout(); } + const { mutate } = useMutation({ + mutationFn: logoutMutation, + onSuccess: () => { + dispatch(clientSignOut()); + router.replace(routePaths.signIn()); + }, + // TODO: update error handling + onError: (error: Error) => { + dispatch( + onOpenModal({ type: "error", content: { message: error.message } }), + ); + }, + }); + const handleDropDownClick = (event: React.MouseEvent) => { event.stopPropagation(); }; diff --git a/src/components/providers/TanstackQueryProvider.tsx b/src/components/providers/TanstackQueryProvider.tsx new file mode 100644 index 000000000..324689e59 --- /dev/null +++ b/src/components/providers/TanstackQueryProvider.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { useState } from "react"; + +interface TanstackQueryProviderProps { + children: React.ReactNode; +} + +export function TanstackQueryProvider({ + children, +}: TanstackQueryProviderProps) { + const [queryClient] = useState(() => new QueryClient()); + + return ( + + {children} + + + ); +} diff --git a/yarn.lock b/yarn.lock index eea6ecb52..34068b532 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4252,6 +4252,54 @@ __metadata: languageName: node linkType: hard +"@tanstack/eslint-plugin-query@npm:^5.59.20": + version: 5.59.20 + resolution: "@tanstack/eslint-plugin-query@npm:5.59.20" + dependencies: + "@typescript-eslint/utils": "npm:^8.3.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + checksum: 95ded76098059b59ba7c4d47841c84a233b75e10b68261d01a5bce92afe1a70873e2fe91ef7650f9703aeb2d5f56fca29a56e7ee8d55c24f5b7437efdc2c80ea + languageName: node + linkType: hard + +"@tanstack/query-core@npm:5.59.20": + version: 5.59.20 + resolution: "@tanstack/query-core@npm:5.59.20" + checksum: c93a8d41e21db532e92c1c90916bec578729d32d1f39d655603b9e81a9d5aebc8588a4d6a75928e04d3ddc90e71a1f8dc066a61d0ba24108eaf60cc2ce024a2f + languageName: node + linkType: hard + +"@tanstack/query-devtools@npm:5.59.20": + version: 5.59.20 + resolution: "@tanstack/query-devtools@npm:5.59.20" + checksum: e90008af9c5754bacb19b78b54bbbb60b4ef4aeae8ff46349cbf8596f50a49b7a66db5b9c5fc4e5d9eba6d1fe4991f0178c1c4eacdc3bee5b8f4b48f50997174 + languageName: node + linkType: hard + +"@tanstack/react-query-devtools@npm:^5.59.20": + version: 5.59.20 + resolution: "@tanstack/react-query-devtools@npm:5.59.20" + dependencies: + "@tanstack/query-devtools": "npm:5.59.20" + peerDependencies: + "@tanstack/react-query": ^5.59.20 + react: ^18 || ^19 + checksum: bd49b0b66840dcf3e6939f78da9707f84d5c1da27a1e51e3ea1e389485d07829807b8b4ec11e11d4d30f709f7e82d732b121fa95715e809f312ef517c49cce7b + languageName: node + linkType: hard + +"@tanstack/react-query@npm:^5.59.20": + version: 5.59.20 + resolution: "@tanstack/react-query@npm:5.59.20" + dependencies: + "@tanstack/query-core": "npm:5.59.20" + peerDependencies: + react: ^18 || ^19 + checksum: fc3342c3a26c51c866d54082d14f86b2f644847ea8f9051000e529a012ea5437e18e35f999fcc453b2eecb0faaf29bd1aaec0956587ade63ba02262d6737800a + languageName: node + linkType: hard + "@testing-library/dom@npm:^9.0.0, @testing-library/dom@npm:^9.3.1": version: 9.3.3 resolution: "@testing-library/dom@npm:9.3.3" @@ -4995,6 +5043,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:8.13.0": + version: 8.13.0 + resolution: "@typescript-eslint/scope-manager@npm:8.13.0" + dependencies: + "@typescript-eslint/types": "npm:8.13.0" + "@typescript-eslint/visitor-keys": "npm:8.13.0" + checksum: 1924b3e740e244d98f8a99740b4196d23ae3263303b387c66db94e140455a3132e603a130f3f70fc71e37f4bda5d0c0c67224ae3911908b097ef3f972c136be4 + languageName: node + linkType: hard + "@typescript-eslint/type-utils@npm:6.14.0": version: 6.14.0 resolution: "@typescript-eslint/type-utils@npm:6.14.0" @@ -5026,6 +5084,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:8.13.0": + version: 8.13.0 + resolution: "@typescript-eslint/types@npm:8.13.0" + checksum: bd3f88b738a92b2222f388bcf831357ef8940a763c2c2eb1947767e1051dd2f8bee387020e8cf4c2309e4142353961b659abc2885e30679109a0488b0bfefc23 + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" @@ -5062,6 +5127,25 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/typescript-estree@npm:8.13.0": + version: 8.13.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.13.0" + dependencies: + "@typescript-eslint/types": "npm:8.13.0" + "@typescript-eslint/visitor-keys": "npm:8.13.0" + debug: "npm:^4.3.4" + fast-glob: "npm:^3.3.2" + is-glob: "npm:^4.0.3" + minimatch: "npm:^9.0.4" + semver: "npm:^7.6.0" + ts-api-utils: "npm:^1.3.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 2d45bc5ed4ac352bea927167ac28ef23bd13b6ae352ff50e85cddfdc4b06518f1dd4ae5f2495e30d6f62d247987677a4e807065d55829ba28963908a821dc96d + languageName: node + linkType: hard + "@typescript-eslint/utils@npm:6.14.0": version: 6.14.0 resolution: "@typescript-eslint/utils@npm:6.14.0" @@ -5097,6 +5181,20 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:^8.3.0": + version: 8.13.0 + resolution: "@typescript-eslint/utils@npm:8.13.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.4.0" + "@typescript-eslint/scope-manager": "npm:8.13.0" + "@typescript-eslint/types": "npm:8.13.0" + "@typescript-eslint/typescript-estree": "npm:8.13.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + checksum: 3fc5a7184a949df5f5b64f6af039a1d21ef7fe15f3d88a5d485ccbb535746d18514751143993a5aee287228151be3e326baf8f899a0a0a93368f6f20857ffa6d + languageName: node + linkType: hard + "@typescript-eslint/visitor-keys@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" @@ -5117,6 +5215,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:8.13.0": + version: 8.13.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.13.0" + dependencies: + "@typescript-eslint/types": "npm:8.13.0" + eslint-visitor-keys: "npm:^3.4.3" + checksum: 50b35f3cf673aaed940613f0007f7c4558a89ebef15c49824e65b6f084b700fbf01b01a4e701e24bbe651297a39678645e739acd255255f1603867a84bef0383 + languageName: node + linkType: hard + "@vitest/expect@npm:^0.34.2": version: 0.34.7 resolution: "@vitest/expect@npm:0.34.7" @@ -6706,6 +6814,9 @@ __metadata: "@storybook/nextjs": "npm:^7.6.4" "@storybook/react": "npm:^7.6.4" "@storybook/test": "npm:^7.6.4" + "@tanstack/eslint-plugin-query": "npm:^5.59.20" + "@tanstack/react-query": "npm:^5.59.20" + "@tanstack/react-query-devtools": "npm:^5.59.20" "@testing-library/jest-dom": "npm:^5.17.0" "@testing-library/react": "npm:^14.0.0" "@types/node": "npm:20.4.5" @@ -8704,7 +8815,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.2": +"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.2, eslint-visitor-keys@npm:^3.4.3": version: 3.4.3 resolution: "eslint-visitor-keys@npm:3.4.3" checksum: 92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 @@ -9039,7 +9150,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.5, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.1": +"fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.5, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.1, fast-glob@npm:^3.3.2": version: 3.3.2 resolution: "fast-glob@npm:3.3.2" dependencies: @@ -12213,6 +12324,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^9.0.4": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed + languageName: node + linkType: hard + "minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.8": version: 1.2.8 resolution: "minimist@npm:1.2.8" @@ -14788,6 +14908,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.6.0": + version: 7.6.3 + resolution: "semver@npm:7.6.3" + bin: + semver: bin/semver.js + checksum: 88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf + languageName: node + linkType: hard + "send@npm:0.18.0": version: 0.18.0 resolution: "send@npm:0.18.0" @@ -15926,6 +16055,15 @@ __metadata: languageName: node linkType: hard +"ts-api-utils@npm:^1.3.0": + version: 1.4.0 + resolution: "ts-api-utils@npm:1.4.0" + peerDependencies: + typescript: ">=4.2.0" + checksum: 1b2bfa50ea52771d564bb143bb69010d25cda03ed573095fbac9b86f717012426443af6647e00e3db70fca60360482a30c1be7cf73c3521c321f6bf5e3594ea0 + languageName: node + linkType: hard + "ts-dedent@npm:^2.0.0, ts-dedent@npm:^2.2.0": version: 2.2.0 resolution: "ts-dedent@npm:2.2.0"