diff --git a/e2e/settings/updating-settings.spec.ts b/e2e/settings/updating-settings.spec.ts index 8fedcf8..769c159 100644 --- a/e2e/settings/updating-settings.spec.ts +++ b/e2e/settings/updating-settings.spec.ts @@ -8,12 +8,12 @@ test.describe('update user settings', () => { // Arrange await settingsPage.goto() // Act - await settingsPage.editName('John Doe') - await settingsPage.submit() + await settingsPage.edit('name', 'John Doe') + await settingsPage.submit('name') await page.reload() // Assert await expect(page).toHaveURL('/settings/profile') - await expect(page.getByPlaceholder('Nombre')).toHaveValue('John Doe') + await expect(page.getByLabel('name')).toHaveValue('John Doe') }) }) diff --git a/e2e/tests/pages/settings.page.ts b/e2e/tests/pages/settings.page.ts index c2d711e..84cc8cb 100644 --- a/e2e/tests/pages/settings.page.ts +++ b/e2e/tests/pages/settings.page.ts @@ -11,12 +11,16 @@ class SettingsPage { await this.page.goto('/settings/profile') } - async editName(name: string) { - await this.page.getByPlaceholder('Nombre').fill(name) + async edit(label: string, name: string) { + await this.page.getByLabel(label).fill(name) } - async submit() { - await this.page.getByRole('button', { name: 'Enviar' }).click() + async submit(label: string) { + await this.page + .getByRole('region') + .filter({ has: this.page.getByLabel(label) }) + .getByRole('button', { name: 'Guardar' }) + .click() } async restore() { diff --git a/src/app/settings/layout.tsx b/src/app/settings/layout.tsx index 1d3fb23..a725d01 100644 --- a/src/app/settings/layout.tsx +++ b/src/app/settings/layout.tsx @@ -1,39 +1,25 @@ 'use client' -import { Card, CardBody, CardHeader, Tab, Tabs } from '@nextui-org/react' -import NextLink from 'next/link' import { usePathname } from 'next/navigation' import { ReactNode } from 'react' +import SettingsTemplate from '@/components/settings-template/settings-template' + +const TABS = [ + { + target: 'profile', + title: 'Perfil', + }, +] + export default function Layout({ children }: { children: ReactNode }) { const pathname = usePathname() return ( <> - - -
Ajustes de usuario
-
- - - - -
{children}
-
-
+ + {children} + ) } diff --git a/src/app/settings/profile/page.tsx b/src/app/settings/profile/page.tsx index 6f3e9de..d0d5920 100644 --- a/src/app/settings/profile/page.tsx +++ b/src/app/settings/profile/page.tsx @@ -1,6 +1,7 @@ import { redirect } from 'next/navigation' -import EditProfileForm from '@/components/edit-profile-form/edit-profile-form' +import SettingsForm from '@/components/settings-form/settings-form' +import SettingsFormInputText from '@/components/settings-form/settings-form-input-text' import UserResponse from '@/core/user/dto/responses/user.response' import { findUser } from '@/core/user/infrastructure/actions/find-user' import { auth } from '@/lib/auth/auth' @@ -16,7 +17,31 @@ export default async function Page() { return ( <> - +
+ +
Introduce tu nombre completo o con el que más se te conoce.
+ +
+ + +
Esta es la dirección de acceso a la plataforma.
+ +
+
) } diff --git a/src/core/user/application/update-setting.use-case.ts b/src/core/user/application/update-setting.use-case.ts new file mode 100644 index 0000000..c7e9e2b --- /dev/null +++ b/src/core/user/application/update-setting.use-case.ts @@ -0,0 +1,46 @@ +import { err, ok, Result } from 'neverthrow' + +import NotFoundError from '@/core/common/domain/errors/application/not-found-error' +import ApplicationError from '@/core/common/domain/errors/application-error' +import DomainError from '@/core/common/domain/errors/domain-error' +import Email from '@/core/common/domain/value-objects/email' +import FullName from '@/core/common/domain/value-objects/fullname' +import User from '@/core/user/domain/model/user.entity' +import Users from '@/core/user/domain/services/users.repository' +import UpdateSettingRequest from '@/core/user/dto/requests/update-setting.request' + +export default class UpdateSettingUseCase { + constructor(private readonly users: Users) {} + + async with( + command: UpdateSettingRequest, + ): Promise> { + return Email.create(command.email) + .asyncAndThen((email) => this.users.findByEmail(email)) + .andThen((user) => this.updateUser(user, command)) + .andThen((user) => this.users.save(user)) + } + + private updateUser(user: User, command: UpdateSettingRequest) { + switch (command.field) { + case 'name': { + return this.updateFullName(command, user) + } + default: { + return err( + new ApplicationError( + `Field ${command.field} does not exists in User entity`, + ), + ) + } + } + } + + private updateFullName(command: UpdateSettingRequest, user: User) { + return FullName.create(command.value).andThen((fullName) => { + user.name = fullName + + return ok(user) + }) + } +} diff --git a/src/core/user/dto/requests/update-setting.request.ts b/src/core/user/dto/requests/update-setting.request.ts new file mode 100644 index 0000000..6a6c573 --- /dev/null +++ b/src/core/user/dto/requests/update-setting.request.ts @@ -0,0 +1,13 @@ +import { DeepReadonly } from 'ts-essentials' + +type UpdateSettingRequest = DeepReadonly<{ + email: string + field: string + value: string +}> + +const UpdateSettingRequest = { + with: (properties: UpdateSettingRequest): UpdateSettingRequest => properties, +} + +export default UpdateSettingRequest diff --git a/src/core/user/infrastructure/actions/update-setting.ts b/src/core/user/infrastructure/actions/update-setting.ts new file mode 100644 index 0000000..1488ebc --- /dev/null +++ b/src/core/user/infrastructure/actions/update-setting.ts @@ -0,0 +1,75 @@ +'use server' + +import { revalidateTag } from 'next/cache' +import { z } from 'zod' + +import UpdateSettingRequest from '@/core/user/dto/requests/update-setting.request' +import me from '@/core/user/infrastructure/actions/me' +import container from '@/lib/container' +import FormResponse from '@/lib/zod/form-response' + +export type UpdatableFields = 'name' | 'email' + +interface UpdateSettingForm { + field: UpdatableFields + value: string +} + +const UpdateSettingMap = { + name: z.object({ + field: z.string(), + value: z.string().min(1).max(64), + }), +} + +type UpdateSettingMapKeys = keyof typeof UpdateSettingMap + +export async function updateSetting( + previousState: FormResponse, + formData: FormData, +): Promise> { + const user = await me() + const { field } = previousState.data + + if (!user) { + return FormResponse.custom( + [field], + 'Error en la sesión del usuario', + previousState.data, + ) + } + + const { email } = user + + if (!(field in UpdateSettingMap)) { + return FormResponse.custom( + [field], + 'El campo no es válido', + previousState.data, + ) + } + + const result = UpdateSettingMap[field as UpdateSettingMapKeys].safeParse( + Object.fromEntries(formData), + ) + + if (!result.success) { + return FormResponse.withError( + result.error, + previousState.data, + ) + } + + const { value } = result.data + + await container.updateSetting.with( + UpdateSettingRequest.with({ email, field, value }), + ) + + revalidateTag(`role-for-${email}`) + + return FormResponse.success( + result.data as UpdateSettingForm, + 'Perfil actualizado.', + ) +} diff --git a/src/lib/container/container.ts b/src/lib/container/container.ts index 54d7ef6..99ff032 100644 --- a/src/lib/container/container.ts +++ b/src/lib/container/container.ts @@ -1,6 +1,6 @@ import CreateBookUseCase from '@/core/book/application/create-book.use-case' import EditBookUseCase from '@/core/book/application/edit-book.use-case' -import { LoanBookUseCase } from '@/core/book/application/loan-book-use.case' +import LoanBookUseCase from '@/core/book/application/loan-book-use.case' import ReturnBookUseCase from '@/core/book/application/return-book.use-case' import FindAllBooksQuery from '@/core/book/infrastructure/queries/find-all-books.query' import FindBookQuery from '@/core/book/infrastructure/queries/find-book.query' @@ -11,6 +11,7 @@ import GetHistoricalLoansQuery from '@/core/loan/infrastructure/queries/get-hist import LoansPrisma from '@/core/loan/infrastructure/services/loans-prisma.repository' import EnableUserUseCase from '@/core/user/application/enable-user.use-case' import FindUserUseCase from '@/core/user/application/find-user.use-case' +import UpdateSettingUseCase from '@/core/user/application/update-setting.use-case' import UpdateUserUseCase from '@/core/user/application/update-user.use-case' import FindAllUsersQuery from '@/core/user/infrastructure/queries/find-all-users.query' import UsersPrisma from '@/core/user/infrastructure/services/users-prisma.repository' @@ -35,6 +36,7 @@ const Container = { getHistoricalLoans: new GetHistoricalLoansQuery(prisma), loanBook: new LoanBookUseCase(books, loanBookService), returnBook: new ReturnBookUseCase(books, returnBookService), + updateSetting: new UpdateSettingUseCase(users), updateUser: new UpdateUserUseCase(users), } },