From 656a913bba6992aeb48b65d59a44dd77f24e8393 Mon Sep 17 00:00:00 2001 From: Maciej Krawczyk <63869461+wzarek@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:46:29 +0200 Subject: [PATCH] [RSN-72] - Login/register logic (#69) * feat: add login and register logic to components TODO: Add same logic to organizer registration. * feat: changed register page styling, added error handling * chore: linting --------- Co-authored-by: raczu --- Client/reasn-client/apps/web/.env.sample | 1 + .../reasn-client/apps/web/app/login/action.ts | 40 ++ .../reasn-client/apps/web/app/login/page.tsx | 40 +- .../apps/web/app/register/action.ts | 62 +++ .../apps/web/app/register/organizer/page.tsx | 215 +++++++-- .../apps/web/app/register/user/page.tsx | 213 ++++++-- Client/reasn-client/apps/web/lib/request.ts | 2 +- Client/reasn-client/apps/web/lib/session.ts | 2 +- Client/reasn-client/package.json | 2 +- .../__tests__/schemas/AddressDto.test.ts | 130 ++--- .../common/__tests__/schemas/UserDto.test.ts | 454 +++++++++--------- .../common/src/helpers/errorHelpers.ts | 45 ++ .../packages/common/src/schemas/AddressDto.ts | 10 +- .../common/src/schemas/RegisterRequest.ts | 2 +- .../packages/common/src/schemas/UserDto.ts | 18 +- .../ui/src/components/shared/Toast.tsx | 17 + .../ui/src/components/shared/form/Button.tsx | 8 +- .../ui/src/components/shared/form/Input.tsx | 29 +- .../ui/src/components/shared/index.tsx | 1 + Client/reasn-client/yarn.lock | 60 +-- 20 files changed, 891 insertions(+), 460 deletions(-) create mode 100644 Client/reasn-client/apps/web/.env.sample create mode 100644 Client/reasn-client/apps/web/app/login/action.ts create mode 100644 Client/reasn-client/apps/web/app/register/action.ts create mode 100644 Client/reasn-client/packages/common/src/helpers/errorHelpers.ts create mode 100644 Client/reasn-client/packages/ui/src/components/shared/Toast.tsx diff --git a/Client/reasn-client/apps/web/.env.sample b/Client/reasn-client/apps/web/.env.sample new file mode 100644 index 00000000..148087c9 --- /dev/null +++ b/Client/reasn-client/apps/web/.env.sample @@ -0,0 +1 @@ +REASN_API_URL=https://localhost:5272 \ No newline at end of file diff --git a/Client/reasn-client/apps/web/app/login/action.ts b/Client/reasn-client/apps/web/app/login/action.ts new file mode 100644 index 00000000..8a901da2 --- /dev/null +++ b/Client/reasn-client/apps/web/app/login/action.ts @@ -0,0 +1,40 @@ +"use server"; + +import { LoginRequestSchema } from "@reasn/common/src/schemas/LoginRequest"; +import { login } from "@/services/auth"; +import { setToken } from "@/lib/token"; +import { redirect } from "next/navigation"; +import { + formatZodError, + handleErrorMessage, +} from "@reasn/common/src/helpers/errorHelpers"; + +export type LoginFormState = { + message?: string | null; + errors?: string | {}; +}; + +export const loginAction = async ( + prevState: any, + formData: FormData, +): Promise => { + const result = LoginRequestSchema.safeParse({ + email: formData.get("email"), + password: formData.get("password"), + }); + + if (!result.success) { + return { + errors: formatZodError(result.error), + message: "Niepoprawne wartości formularza.", + }; + } + + try { + const payload = await login(result.data); + setToken(payload); + redirect("/"); + } catch (e) { + return handleErrorMessage(e); + } +}; diff --git a/Client/reasn-client/apps/web/app/login/page.tsx b/Client/reasn-client/apps/web/app/login/page.tsx index c9e0f136..582d690d 100644 --- a/Client/reasn-client/apps/web/app/login/page.tsx +++ b/Client/reasn-client/apps/web/app/login/page.tsx @@ -5,20 +5,40 @@ import { FloatingInput, } from "@reasn/ui/src/components/shared/form"; import Link from "next/link"; -import React, { useRef } from "react"; +import React, { useEffect, useRef, useState } from "react"; +import { useFormState } from "react-dom"; +import { LoginFormState, loginAction } from "@/app/login/action"; +import { Toast } from "@reasn/ui/src/components/shared"; const LoginPage = () => { + const initialState = { message: null, errors: {} }; const formRef = useRef(null); + const [state, formAction] = useFormState(loginAction, initialState); + const [error, setError] = useState({}); - const handleFormSubmit = () => { - console.log("form submitted"); - formRef.current?.submit(); + const handleFormAction = async (formData: FormData) => { + const res = await loginAction(state, formData); + if (res?.message) { + setError(res); + } }; + useEffect(() => { + if (error?.message) { + setTimeout(() => { + setError({}); + }, 5000); + } + }, [error]); + return ( <>
-
+
@@ -33,9 +53,17 @@ const LoginPage = () => {

miło, że do nas wracasz

- + formRef.current?.requestSubmit()} + />
+
+ {error?.message && ( + + )} +
); }; diff --git a/Client/reasn-client/apps/web/app/register/action.ts b/Client/reasn-client/apps/web/app/register/action.ts new file mode 100644 index 00000000..c7b6bb7c --- /dev/null +++ b/Client/reasn-client/apps/web/app/register/action.ts @@ -0,0 +1,62 @@ +"use server"; + +import { + RegisterRequestSchema, + RegisterRequestMapper, +} from "@reasn/common/src/schemas/RegisterRequest"; +import { register } from "@/services/auth"; +import { redirect } from "next/navigation"; +import { z } from "zod"; +import { + formatZodError, + handleErrorMessage, +} from "@reasn/common/src/helpers/errorHelpers"; + +export type RegisterFormState = { + message?: string | null; + errors?: string | {}; +}; + +const RegisterRequestExtendedSchema = RegisterRequestSchema.extend({ + confirmPassword: z.string(), +}).refine((data) => data.password === data.confirmPassword); + +export type RegisterFormData = z.infer; + +export const registerAction = async ( + prevState: any, + formData: FormData, +): Promise => { + const result = RegisterRequestExtendedSchema.safeParse({ + name: formData.get("name"), + surname: formData.get("surname"), + email: formData.get("email"), + username: formData.get("username"), + password: formData.get("password"), + confirmPassword: formData.get("confirmPassword"), + phone: formData.get("phone"), + address: { + country: formData.get("country"), + city: formData.get("city"), + street: formData.get("street"), + state: formData.get("state"), + zipCode: formData.get("postcode"), + }, + role: formData.get("role"), + }); + + if (!result.success) { + console.log(result.error); + return { + errors: formatZodError(result.error), + message: "Niepoprawne wartości formularza.", + }; + } + + try { + await register(RegisterRequestMapper.fromObject(result.data)); + redirect("/login"); + } catch (e) { + return handleErrorMessage(e); + } +}; diff --git a/Client/reasn-client/apps/web/app/register/organizer/page.tsx b/Client/reasn-client/apps/web/app/register/organizer/page.tsx index be2f7ce2..2f519710 100644 --- a/Client/reasn-client/apps/web/app/register/organizer/page.tsx +++ b/Client/reasn-client/apps/web/app/register/organizer/page.tsx @@ -4,72 +4,181 @@ import { ButtonBase, FloatingInput, } from "@reasn/ui/src/components/shared/form"; -import React, { useRef, useState } from "react"; +import React, { useRef, useState, ChangeEvent, useEffect } from "react"; +import { useFormState } from "react-dom"; +import { RegisterFormState, registerAction } from "@/app/register/action"; +import { UserRole } from "@reasn/common/src/enums/schemasEnums"; +import { Toast } from "@reasn/ui/src/components/shared"; const RegisterOrganizer = () => { + const initialState = { message: null, errors: {} }; const [currentStep, setCurrentStep] = useState(1); - const formRef = useRef(null); + const [state, formAction] = useFormState(registerAction, initialState); + const [formData, setFormData] = useState({ + name: "", + surname: "", + email: "", + username: "", + password: "", + confirmPassword: "", + phone: "", + country: "", + city: "", + street: "", + state: "", + postcode: "", + role: UserRole.ORGANIZER, + }); + const [error, setError] = useState({}); + + const handleChange = (e: ChangeEvent) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value, + }); + }; - const handleFormSubmit = () => { - console.log("form submitted"); - formRef.current?.submit(); + const handleFormAction = async (formData: FormData) => { + const res = await registerAction(state, formData); + if (res?.message) { + setError(res); + } }; + useEffect(() => { + if (error?.message) { + setTimeout(() => { + setError({}); + }, 5000); + } + }, [error]); + + const formRef = useRef(null); + return ( <> -
- - {currentStep === 1 && ( - <> - - - - - - - - - )} - {currentStep === 2 && ( - <> - - - - - - - )} +
+ + + + + + + + + + + + + +
- {currentStep === 1 && ( -

- to jak, zorganizujesz nam coś? -

- )} - {currentStep === 2 && ( -

- gdzie możemy cię znaleźć? -

- )} - - currentStep === 2 - ? handleFormSubmit() - : setCurrentStep(currentStep + 1) - } - /> +

+ {currentStep === 1 + ? "to jak, zorganizujesz nam coś?" + : "gdzie możemy cię znaleźć?"} +

+
+ {currentStep > 1 && ( + setCurrentStep(currentStep - 1)} + /> + )} + + currentStep === 2 + ? formRef.current?.requestSubmit() + : setCurrentStep(currentStep + 1) + } + /> +
+
+ {error?.message && ( + + )} +
); }; diff --git a/Client/reasn-client/apps/web/app/register/user/page.tsx b/Client/reasn-client/apps/web/app/register/user/page.tsx index 78431412..a7fdae40 100644 --- a/Client/reasn-client/apps/web/app/register/user/page.tsx +++ b/Client/reasn-client/apps/web/app/register/user/page.tsx @@ -4,72 +4,181 @@ import { ButtonBase, FloatingInput, } from "@reasn/ui/src/components/shared/form"; -import React, { useRef, useState } from "react"; +import React, { useRef, useState, ChangeEvent, useEffect } from "react"; +import { useFormState } from "react-dom"; +import { RegisterFormState, registerAction } from "@/app/register/action"; +import { UserRole } from "@reasn/common/src/enums/schemasEnums"; +import { Toast } from "@reasn/ui/src/components/shared"; const RegisterUser = () => { + const initialState = { message: null, errors: {} }; const [currentStep, setCurrentStep] = useState(1); - const formRef = useRef(null); + const [state, formAction] = useFormState(registerAction, initialState); + const [formData, setFormData] = useState({ + name: "", + surname: "", + email: "", + username: "", + password: "", + confirmPassword: "", + phone: "", + country: "", + city: "", + street: "", + state: "", + postcode: "", + role: UserRole.USER, + }); + const [error, setError] = useState({}); + + const handleChange = (e: ChangeEvent) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value, + }); + }; - const handleFormSubmit = () => { - console.log("form submitted"); - formRef.current?.submit(); + const handleFormAction = async (formData: FormData) => { + const res = await registerAction(state, formData); + if (res?.message) { + setError(res); + } }; + useEffect(() => { + if (error?.message) { + setTimeout(() => { + setError({}); + }, 5000); + } + }, [error]); + + const formRef = useRef(null); + return ( <>
-
- {currentStep === 1 && ( - <> - - - - - - - - - )} - {currentStep === 2 && ( - <> - - - - - - - )} + + + + + + + + + + + + + +
- {currentStep === 1 && ( -

- znalazłeś już swój powód do spotkań? -

- )} - {currentStep === 2 && ( -

- gdzie powinniśmy cię szukać? -

- )} - - currentStep === 2 - ? handleFormSubmit() - : setCurrentStep(currentStep + 1) - } - /> +

+ {currentStep === 1 + ? "znalazłeś już swój powód do spotkań?" + : "gdzie powinniśmy cię szukać?"} +

+
+ {currentStep > 1 && ( + setCurrentStep(currentStep - 1)} + /> + )} + + currentStep === 2 + ? formRef.current?.requestSubmit() + : setCurrentStep(currentStep + 1) + } + /> +
+
+ {error?.message && ( + + )} +
); }; diff --git a/Client/reasn-client/apps/web/lib/request.ts b/Client/reasn-client/apps/web/lib/request.ts index 05c26ba1..cbed62d3 100644 --- a/Client/reasn-client/apps/web/lib/request.ts +++ b/Client/reasn-client/apps/web/lib/request.ts @@ -74,6 +74,6 @@ export const sendRequest = async ( console.error( `Error while sending request to ${url} with method ${method}: ${error}`, ); - throw Error; + throw Error(`Error while sending request to ${url}`); } }; diff --git a/Client/reasn-client/apps/web/lib/session.ts b/Client/reasn-client/apps/web/lib/session.ts index ecdde0e4..d1bf8c64 100644 --- a/Client/reasn-client/apps/web/lib/session.ts +++ b/Client/reasn-client/apps/web/lib/session.ts @@ -1,5 +1,5 @@ import { jwtDecode, JwtPayload } from "jwt-decode"; -import { UserRole } from "@reasn/common/src/enums/modelsEnums"; +import { UserRole } from "@reasn/common/src/enums/schemasEnums"; import { getToken } from "@/lib/token"; export const SESSION_DEFAULT = { diff --git a/Client/reasn-client/package.json b/Client/reasn-client/package.json index d1333d23..30f4d44b 100644 --- a/Client/reasn-client/package.json +++ b/Client/reasn-client/package.json @@ -27,7 +27,7 @@ "prettier-plugin-tailwindcss": "^0.6.1", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", - "turbo": "latest" + "turbo": "^2.0.4" }, "packageManager": "yarn@4.1.0", "engines": { diff --git a/Client/reasn-client/packages/common/__tests__/schemas/AddressDto.test.ts b/Client/reasn-client/packages/common/__tests__/schemas/AddressDto.test.ts index eccb599b..7e849302 100644 --- a/Client/reasn-client/packages/common/__tests__/schemas/AddressDto.test.ts +++ b/Client/reasn-client/packages/common/__tests__/schemas/AddressDto.test.ts @@ -14,21 +14,21 @@ describe("AddressDto", () => { describe("fromJson", () => { it("should create an instance of AddressDto from JSON string", () => { const json = `{ - "Country": "${country}", - "City": "${city}", - "Street": "${street}", - "State": "${state}", - "ZipCode": "${zipCode}" + "country": "${country}", + "city": "${city}", + "street": "${street}", + "state": "${state}", + "zipCode": "${zipCode}" }`; let address = AddressDtoMapper.fromJSON(json); address = address as AddressDto; - expect(address.Country).toBe(country); - expect(address.City).toBe(city); - expect(address.Street).toBe(street); - expect(address.State).toBe(state); - expect(address.ZipCode).toBe(zipCode); + expect(address.country).toBe(country); + expect(address.city).toBe(city); + expect(address.street).toBe(street); + expect(address.state).toBe(state); + expect(address.zipCode).toBe(zipCode); }); it("should throw error if the JSON string is empty", () => { @@ -37,38 +37,38 @@ describe("AddressDto", () => { it("should throw an error when providing JSON without each property individually", () => { const jsonWithoutCountry = `{ - "City": "${city}", - "Street": "${street}", - "State": "${state}", - "ZipCode": "${zipCode}" + "city": "${city}", + "street": "${street}", + "state": "${state}", + "zipCode": "${zipCode}" }`; const jsonWithoutCity = `{ - "Country": "${country}", - "Street": "${street}", - "State": "${state}", - "ZipCode": "${zipCode}" + "country": "${country}", + "street": "${street}", + "state": "${state}", + "zipCode": "${zipCode}" }`; const jsonWithoutStreet = `{ - "Country": "${country}", - "City": "${city}", - "State": "${state}", - "ZipCode": "${zipCode}" + "country": "${country}", + "city": "${city}", + "state": "${state}", + "zipCode": "${zipCode}" }`; const jsonWithoutState = `{ - "Country": "${country}", - "City": "${city}", - "Street": "${street}", - "ZipCode": "${zipCode}" + "country": "${country}", + "city": "${city}", + "street": "${street}", + "zipCode": "${zipCode}" }`; const jsonWithoutZipCode = `{ - "Country": "${country}", - "City": "${city}", - "Street": "${street}", - "State": "${state}" + "country": "${country}", + "city": "${city}", + "street": "${street}", + "state": "${state}" }`; expect(() => AddressDtoMapper.fromJSON(jsonWithoutCountry)).toThrow( @@ -92,65 +92,65 @@ describe("AddressDto", () => { describe("fromObject", () => { it("should create an instance of AddressDto from an object", () => { const object = { - Country: country, - City: city, - Street: street, - State: state, - ZipCode: zipCode, + country: country, + city: city, + street: street, + state: state, + zipCode: zipCode, }; let address = AddressDtoMapper.fromObject(object); address = address as AddressDto; - expect(address.Country).toBe(country); - expect(address.City).toBe(city); - expect(address.Street).toBe(street); - expect(address.State).toBe(state); - expect(address.ZipCode).toBe(zipCode); + expect(address.country).toBe(country); + expect(address.city).toBe(city); + expect(address.street).toBe(street); + expect(address.state).toBe(state); + expect(address.zipCode).toBe(zipCode); }); it("should throw an error if the object is invalid", () => { const object = { - Country: country, - City: true, - Street: street, - State: state, - ZipCode: null, + country: country, + city: true, + street: street, + state: state, + zipCode: null, }; const objectWithoutCountry = { - City: city, - Street: street, - State: state, - ZipCode: zipCode, + city: city, + street: street, + state: state, + zipCode: zipCode, }; const objectWithoutCity = { - Country: country, - Street: street, - State: state, - ZipCode: zipCode, + country: country, + street: street, + state: state, + zipCode: zipCode, }; const objectWithoutStreet = { - Country: country, - City: city, - State: state, - ZipCode: zipCode, + country: country, + city: city, + state: state, + zipCode: zipCode, }; const objectWithoutState = { - Country: country, - City: city, - Street: street, - ZipCode: zipCode, + country: country, + city: city, + street: street, + zipCode: zipCode, }; const objectWithoutZipCode = { - Country: country, - City: city, - Street: street, - State: state, + country: country, + city: city, + street: street, + state: state, }; expect(() => AddressDtoMapper.fromObject(object)).toThrow( diff --git a/Client/reasn-client/packages/common/__tests__/schemas/UserDto.test.ts b/Client/reasn-client/packages/common/__tests__/schemas/UserDto.test.ts index 31b26838..fcefd5ad 100644 --- a/Client/reasn-client/packages/common/__tests__/schemas/UserDto.test.ts +++ b/Client/reasn-client/packages/common/__tests__/schemas/UserDto.test.ts @@ -13,11 +13,11 @@ describe("UserDto", () => { const role = UserRole.USER; const addressId = 2; const address: AddressDto = { - Country: "Test Country", - City: "Test City", - Street: "Test Street", - State: "Test State", - ZipCode: "12345", + country: "Test country", + city: "Test city", + street: "Test street", + state: "Test state", + zipCode: "12345", }; const interests: UserInterestDto[] = [ { Interest: { Name: "Programming" }, Level: 5 }, @@ -27,29 +27,29 @@ describe("UserDto", () => { describe("fromJson", () => { it("should create an instance of UserDto from JSON string", () => { const json = `{ - "Username": "${username}", - "Name": "${name}", - "Surname": "${surname}", - "Email": "${email}", - "Phone": "${phone}", - "Role": "${role}", - "AddressId": ${addressId}, - "Address": ${JSON.stringify(address)}, - "Intrests": ${JSON.stringify(interests)} + "username": "${username}", + "name": "${name}", + "surname": "${surname}", + "email": "${email}", + "phone": "${phone}", + "role": "${role}", + "addressId": ${addressId}, + "address": ${JSON.stringify(address)}, + "intrests": ${JSON.stringify(interests)} }`; let user = UserDtoMapper.fromJSON(json); user = user as UserDto; - expect(user.Username).toBe(username); - expect(user.Name).toBe(name); - expect(user.Surname).toBe(surname); - expect(user.Email).toBe(email); - expect(user.Phone).toBe(phone); - expect(user.Role).toBe(role); - expect(user.AddressId).toBe(addressId); - expect(user.Address).toEqual(address); - expect(user.Intrests).toEqual(interests); + expect(user.username).toBe(username); + expect(user.name).toBe(name); + expect(user.surname).toBe(surname); + expect(user.email).toBe(email); + expect(user.phone).toBe(phone); + expect(user.role).toBe(role); + expect(user.addressId).toBe(addressId); + expect(user.address).toEqual(address); + expect(user.intrests).toEqual(interests); }); it("should return null if the JSON string is empty", () => { @@ -57,128 +57,128 @@ describe("UserDto", () => { }); it("should throw an error when providing JSON without each property individually", () => { - const jsonWithoutUsername = `{ - "Name": "${name}", - "Surname": "${surname}", - "Email": "${email}", - "Phone": "${phone}", - "Role": "${role}", - "AddressId": ${addressId}, - "Address": ${JSON.stringify(address)}, - "Intrests": ${JSON.stringify(interests)} + const jsonWithoutusername = `{ + "name": "${name}", + "surname": "${surname}", + "email": "${email}", + "phone": "${phone}", + "role": "${role}", + "addressId": ${addressId}, + "address": ${JSON.stringify(address)}, + "intrests": ${JSON.stringify(interests)} }`; - const jsonWithoutName = `{ - "Username": "${username}", - "Surname": "${surname}", - "Email": "${email}", - "Phone": "${phone}", - "Role": "${role}", - "AddressId": ${addressId}, - "Address": ${JSON.stringify(address)}, - "Intrests": ${JSON.stringify(interests)} + const jsonWithoutname = `{ + "username": "${username}", + "surname": "${surname}", + "email": "${email}", + "phone": "${phone}", + "role": "${role}", + "addressId": ${addressId}, + "address": ${JSON.stringify(address)}, + "intrests": ${JSON.stringify(interests)} }`; - const jsonWithoutSurname = `{ - "Username": "${username}", - "Name": "${name}", - "Email": "${email}", - "Phone": "${phone}", - "Role": "${role}", - "AddressId": ${addressId}, - "Address": ${JSON.stringify(address)}, - "Intrests": ${JSON.stringify(interests)} + const jsonWithoutsurname = `{ + "username": "${username}", + "name": "${name}", + "email": "${email}", + "phone": "${phone}", + "role": "${role}", + "addressId": ${addressId}, + "address": ${JSON.stringify(address)}, + "intrests": ${JSON.stringify(interests)} }`; - const jsonWithoutEmail = `{ - "Username": "${username}", - "Name": "${name}", - "Surname": "${surname}", - "Phone": "${phone}", - "Role": "${role}", - "AddressId": ${addressId}, - "Address": ${JSON.stringify(address)}, - "Intrests": ${JSON.stringify(interests)} + const jsonWithoutemail = `{ + "username": "${username}", + "name": "${name}", + "surname": "${surname}", + "phone": "${phone}", + "role": "${role}", + "addressId": ${addressId}, + "address": ${JSON.stringify(address)}, + "intrests": ${JSON.stringify(interests)} }`; - const jsonWithoutPhone = `{ - "Username": "${username}", - "Name": "${name}", - "Surname": "${surname}", - "Email": "${email}", - "Role": "${role}", - "AddressId": ${addressId}, - "Intrests": ${JSON.stringify(interests)} + const jsonWithoutphone = `{ + "username": "${username}", + "name": "${name}", + "surname": "${surname}", + "email": "${email}", + "role": "${role}", + "addressId": ${addressId}, + "intrests": ${JSON.stringify(interests)} }`; - const jsonWithoutRole = `{ - "Username": "${username}", - "Name": "${name}", - "Surname": "${surname}", - "Email": "${email}", - "Phone": "${phone}", - "AddressId": ${addressId}, - "Address": ${JSON.stringify(address)}, - "Intrests": ${JSON.stringify(interests)} + const jsonWithoutrole = `{ + "username": "${username}", + "name": "${name}", + "surname": "${surname}", + "email": "${email}", + "phone": "${phone}", + "addressId": ${addressId}, + "address": ${JSON.stringify(address)}, + "intrests": ${JSON.stringify(interests)} }`; - const jsonWithoutAddressId = `{ - "Username": "${username}", - "Name": "${name}", - "Surname": "${surname}", - "Email": "${email}", - "Phone": "${phone}", - "Role": "${role}", - "Address": ${JSON.stringify(address)}, - "Intrests": ${JSON.stringify(interests)} + const jsonWithoutaddressId = `{ + "username": "${username}", + "name": "${name}", + "surname": "${surname}", + "email": "${email}", + "phone": "${phone}", + "role": "${role}", + "address": ${JSON.stringify(address)}, + "intrests": ${JSON.stringify(interests)} }`; - const jsonWithoutAddress = `{ - "Username": "${username}", - "Name": "${name}", - "Surname": "${surname}", - "Email": "${email}", - "Phone": "${phone}", - "AddressId": ${addressId}, - "Role": "${role}", - "Intrests": ${JSON.stringify(interests)} + const jsonWithoutaddress = `{ + "username": "${username}", + "name": "${name}", + "surname": "${surname}", + "email": "${email}", + "phone": "${phone}", + "addressId": ${addressId}, + "role": "${role}", + "intrests": ${JSON.stringify(interests)} }`; - const jsonWithoutInterests = `{ - "Username": "${username}", - "Name": "${name}", - "Surname": "${surname}", - "Email": "${email}", - "Phone": "${phone}", - "Role": "${role}", - "AddressId": ${addressId} + const jsonWithoutinterests = `{ + "username": "${username}", + "name": "${name}", + "surname": "${surname}", + "email": "${email}", + "phone": "${phone}", + "role": "${role}", + "addressId": ${addressId} }`; - expect(() => UserDtoMapper.fromJSON(jsonWithoutUsername)).toThrow( + expect(() => UserDtoMapper.fromJSON(jsonWithoutusername)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromJSON(jsonWithoutName)).toThrow( + expect(() => UserDtoMapper.fromJSON(jsonWithoutname)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromJSON(jsonWithoutSurname)).toThrow( + expect(() => UserDtoMapper.fromJSON(jsonWithoutsurname)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromJSON(jsonWithoutEmail)).toThrow( + expect(() => UserDtoMapper.fromJSON(jsonWithoutemail)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromJSON(jsonWithoutPhone)).toThrow( + expect(() => UserDtoMapper.fromJSON(jsonWithoutphone)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromJSON(jsonWithoutRole)).toThrow( + expect(() => UserDtoMapper.fromJSON(jsonWithoutrole)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromJSON(jsonWithoutAddressId)).toThrow( + expect(() => UserDtoMapper.fromJSON(jsonWithoutaddressId)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromJSON(jsonWithoutAddress)).toThrow( + expect(() => UserDtoMapper.fromJSON(jsonWithoutaddress)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromJSON(jsonWithoutInterests)).toThrow( + expect(() => UserDtoMapper.fromJSON(jsonWithoutinterests)).toThrow( ModelMappingError, ); }); @@ -187,168 +187,168 @@ describe("UserDto", () => { describe("fromObject", () => { it("should create an instance of UserDto from an object", () => { const object = { - Username: username, - Name: name, - Surname: surname, - Email: email, - Phone: phone, - Role: role, - AddressId: addressId, - Address: address, - Intrests: interests, + username: username, + name: name, + surname: surname, + email: email, + phone: phone, + role: role, + addressId: addressId, + address: address, + intrests: interests, }; let user = UserDtoMapper.fromObject(object); user = user as UserDto; - expect(user.Username).toBe(username); - expect(user.Name).toBe(name); - expect(user.Surname).toBe(surname); - expect(user.Email).toBe(email); - expect(user.Phone).toBe(phone); - expect(user.Role).toBe(role); - expect(user.AddressId).toBe(addressId); - expect(user.Address).toEqual(address); - expect(user.Intrests).toEqual(interests); + expect(user.username).toBe(username); + expect(user.name).toBe(name); + expect(user.surname).toBe(surname); + expect(user.email).toBe(email); + expect(user.phone).toBe(phone); + expect(user.role).toBe(role); + expect(user.addressId).toBe(addressId); + expect(user.address).toEqual(address); + expect(user.intrests).toEqual(interests); }); it("should throw an error if the object is invalid", () => { const object = { - Username: true, - Name: null, - Surname: "Doe", - Email: "john.doe@example.com", - Phone: "+1234567890", - Role: UserRole.USER, - AddressId: 2, - Intrests: interests, + username: true, + name: null, + surname: "Doe", + email: "john.doe@example.com", + phone: "+1234567890", + role: UserRole.USER, + addressId: 2, + intrests: interests, }; - const objectWithoutUsername = { - Name: name, - Surname: surname, - Email: email, - Phone: phone, - Role: role, - AddressId: addressId, - Address: address, - Intrests: interests, + const objectWithoutusername = { + name: name, + surname: surname, + email: email, + phone: phone, + role: role, + addressId: addressId, + address: address, + intrests: interests, }; - const objectWithoutName = { - Username: username, - Surname: surname, - Email: email, - Phone: phone, - Role: role, - AddressId: addressId, - Address: address, - Intrests: interests, + const objectWithoutname = { + username: username, + surname: surname, + email: email, + phone: phone, + role: role, + addressId: addressId, + address: address, + intrests: interests, }; - const objectWithoutSurname = { - Username: username, - Name: name, - Email: email, - Phone: phone, - Role: role, - AddressId: addressId, - Address: address, - Intrests: interests, + const objectWithoutsurname = { + username: username, + name: name, + email: email, + phone: phone, + role: role, + addressId: addressId, + address: address, + intrests: interests, }; - const objectWithoutEmail = { - Username: username, - Name: name, - Surname: surname, - Phone: phone, - Role: role, - AddressId: addressId, - Address: address, - Intrests: interests, + const objectWithoutemail = { + username: username, + name: name, + surname: surname, + phone: phone, + role: role, + addressId: addressId, + address: address, + intrests: interests, }; - const objectWithoutPhone = { - Username: username, - Name: name, - Surname: surname, - Email: email, - Role: role, - AddressId: addressId, - Address: address, - Intrests: interests, + const objectWithoutphone = { + username: username, + name: name, + surname: surname, + email: email, + role: role, + addressId: addressId, + address: address, + intrests: interests, }; - const objectWithoutRole = { - Username: username, - Name: name, - Surname: surname, - Email: email, - Phone: phone, - AddressId: addressId, - Address: address, - Intrests: interests, + const objectWithoutrole = { + username: username, + name: name, + surname: surname, + email: email, + phone: phone, + addressId: addressId, + address: address, + intrests: interests, }; - const objectWithoutAddressId = { - Username: username, - Name: name, - Surname: surname, - Email: email, - Phone: phone, - Role: role, - Address: address, - Intrests: interests, + const objectWithoutaddressId = { + username: username, + name: name, + surname: surname, + email: email, + phone: phone, + role: role, + address: address, + intrests: interests, }; - const objectWithoutAddress = { - Username: username, - Name: name, - Surname: surname, - Email: email, - Phone: phone, - Role: role, - AddressId: addressId, - Intrests: interests, + const objectWithoutaddress = { + username: username, + name: name, + surname: surname, + email: email, + phone: phone, + role: role, + addressId: addressId, + intrests: interests, }; - const objectWithoutInterests = { - Username: username, - Name: name, - Surname: surname, - Email: email, - Phone: phone, - Role: role, - AddressId: addressId, - Address: address, + const objectWithoutinterests = { + username: username, + name: name, + surname: surname, + email: email, + phone: phone, + role: role, + addressId: addressId, + address: address, }; expect(() => UserDtoMapper.fromObject(object)).toThrow(ModelMappingError); - expect(() => UserDtoMapper.fromObject(objectWithoutUsername)).toThrow( + expect(() => UserDtoMapper.fromObject(objectWithoutusername)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromObject(objectWithoutName)).toThrow( + expect(() => UserDtoMapper.fromObject(objectWithoutname)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromObject(objectWithoutSurname)).toThrow( + expect(() => UserDtoMapper.fromObject(objectWithoutsurname)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromObject(objectWithoutEmail)).toThrow( + expect(() => UserDtoMapper.fromObject(objectWithoutemail)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromObject(objectWithoutPhone)).toThrow( + expect(() => UserDtoMapper.fromObject(objectWithoutphone)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromObject(objectWithoutRole)).toThrow( + expect(() => UserDtoMapper.fromObject(objectWithoutrole)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromObject(objectWithoutAddressId)).toThrow( + expect(() => UserDtoMapper.fromObject(objectWithoutaddressId)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromObject(objectWithoutAddress)).toThrow( + expect(() => UserDtoMapper.fromObject(objectWithoutaddress)).toThrow( ModelMappingError, ); - expect(() => UserDtoMapper.fromObject(objectWithoutInterests)).toThrow( + expect(() => UserDtoMapper.fromObject(objectWithoutinterests)).toThrow( ModelMappingError, ); }); diff --git a/Client/reasn-client/packages/common/src/helpers/errorHelpers.ts b/Client/reasn-client/packages/common/src/helpers/errorHelpers.ts new file mode 100644 index 00000000..e80842c7 --- /dev/null +++ b/Client/reasn-client/packages/common/src/helpers/errorHelpers.ts @@ -0,0 +1,45 @@ +import ApiAuthorizationError from "@reasn/common/src/errors/ApiAuthorizationError"; +import ApiConnectionError from "@reasn/common/src/errors/ApiConnectionError"; +import { ZodError, ZodIssue } from "zod"; + +export const handleErrorMessage = (e: any) => { + if (e instanceof ApiAuthorizationError) { + return { + message: "Niepoprawne dane logowania." + e.message, + }; + } + if (e instanceof ApiConnectionError) { + return { + message: "Wystąpił błąd podczas logowania: " + e.message, + }; + } + + if (e instanceof Error) { + return { + message: "Wystąpił błąd podczas logowania: " + e.message, + }; + } + + return { + message: "Wystąpił błąd podczas logowania: ", + }; +}; + +const formatZodIssue = (issue: ZodIssue): string => { + const { path, message } = issue; + const pathString = path.join("."); + + return `${pathString}: ${message}`; +}; + +export const formatZodError = (error: ZodError): string => { + const { issues } = error; + + if (issues.length) { + const currentIssue = issues[0]; + + return formatZodIssue(currentIssue); + } + + return "Niepoprawne wartości formularza."; +}; diff --git a/Client/reasn-client/packages/common/src/schemas/AddressDto.ts b/Client/reasn-client/packages/common/src/schemas/AddressDto.ts index 8ad11d68..a6d4504f 100644 --- a/Client/reasn-client/packages/common/src/schemas/AddressDto.ts +++ b/Client/reasn-client/packages/common/src/schemas/AddressDto.ts @@ -2,23 +2,23 @@ import ModelMappingError from "../errors/ModelMappingError"; import { z } from "zod"; export const AddressDtoSchema = z.object({ - Country: z + country: z .string() .max(64) .regex(/^\p{Lu}[\p{L}\s'-]*(?; diff --git a/Client/reasn-client/packages/common/src/schemas/UserDto.ts b/Client/reasn-client/packages/common/src/schemas/UserDto.ts index 7053027c..37ea88b9 100644 --- a/Client/reasn-client/packages/common/src/schemas/UserDto.ts +++ b/Client/reasn-client/packages/common/src/schemas/UserDto.ts @@ -5,27 +5,27 @@ import { UserRole } from "../enums/schemasEnums"; import { z } from "zod"; export const UserDtoSchema = z.object({ - Username: z + username: z .string() .max(64) .regex(/^[\p{L}\d._%+-]{4,}$/u), - Name: z + name: z .string() .max(64) .regex(/^\p{Lu}[\p{Ll}\s'-]+$/u), - Surname: z + surname: z .string() .max(64) .regex(/^\p{L}+(?:[\s'-]\p{L}+)*$/u), - Email: z.string().email(), - Phone: z + email: z.string().email(), + phone: z .string() .nullable() .refine((value) => value === null || /^\+\d{1,3}\s\d{1,15}$/.test(value)), - Role: z.nativeEnum(UserRole), - AddressId: z.number(), - Address: AddressDtoSchema, - Intrests: z.array(UserInterestDtoSchema), + role: z.nativeEnum(UserRole), + addressId: z.number(), + address: AddressDtoSchema, + intrests: z.array(UserInterestDtoSchema), }); export type UserDto = z.infer; diff --git a/Client/reasn-client/packages/ui/src/components/shared/Toast.tsx b/Client/reasn-client/packages/ui/src/components/shared/Toast.tsx new file mode 100644 index 00000000..ea020019 --- /dev/null +++ b/Client/reasn-client/packages/ui/src/components/shared/Toast.tsx @@ -0,0 +1,17 @@ +import React from "react"; + +interface ToastProps { + message?: string | null; + errors?: string; +} + +export const Toast = (props: ToastProps) => { + const { message, errors } = props; + console.log(errors); + return ( +
+ {message &&
{message}
} + {errors &&
{errors}
} +
+ ); +}; diff --git a/Client/reasn-client/packages/ui/src/components/shared/form/Button.tsx b/Client/reasn-client/packages/ui/src/components/shared/form/Button.tsx index 31ca0eb6..29882937 100644 --- a/Client/reasn-client/packages/ui/src/components/shared/form/Button.tsx +++ b/Client/reasn-client/packages/ui/src/components/shared/form/Button.tsx @@ -5,16 +5,18 @@ import React from "react"; interface ButtonProps { text: string; + type?: "button" | "submit" | "reset"; className?: string; background?: string; - onClick: () => void; + onClick?: () => void; } export const ButtonBase = (props: ButtonProps) => { - const { text, className, background, onClick } = props; + const { text, type, className, background, onClick } = props; return (