diff --git a/src/app/[locale]/unauthorized/page.tsx b/src/app/[locale]/unauthorized/page.tsx new file mode 100644 index 00000000..5b258f62 --- /dev/null +++ b/src/app/[locale]/unauthorized/page.tsx @@ -0,0 +1,27 @@ +'use client'; +import React from 'react'; +import { useRouter } from 'next/navigation'; + +const UnauthorizedPage = () => { + const router = useRouter(); + const handleGoBack = () => { + router.push('/dashboard'); + }; + + return ( +
+
+

Acceso No Autorizado

+

Lo sentimos, no tienes permiso para acceder a esta página.

+ +
+
+ ); +}; + +export default UnauthorizedPage; diff --git a/src/app/[locale]/users/page.tsx b/src/app/[locale]/users/page.tsx index 371b9a55..89678298 100644 --- a/src/app/[locale]/users/page.tsx +++ b/src/app/[locale]/users/page.tsx @@ -2,6 +2,7 @@ import React, { Suspense } from 'react'; import { unstable_setRequestLocale } from 'next-intl/server'; +import { paginationInitialParams } from '@/features/items/constants/paginationInitialParams'; import { LoaderStarsWars } from '@/features/shared/atoms/loader/LoaderStarsWars'; import { UsersTemplate } from '@/features/users/template/UsersTemplate'; import { PrivateLayout } from '@/layout/private-layout/PrivateLayout'; @@ -16,8 +17,13 @@ export default async function Page({ searchParams, params: { locale } }: Props) const params = new URLSearchParams(searchParams); const queryParams: QueryParams = { + pagination: { + offset: params.get('pagination[offset]') ?? paginationInitialParams.offset, + limit: params.get('pagination[limit]') ?? paginationInitialParams.limit, + }, filter: params, }; + unstable_setRequestLocale(locale); return ( diff --git a/src/app/[locale]/users/update/page.tsx b/src/app/[locale]/users/update/page.tsx index e76924c1..89eb2b87 100644 --- a/src/app/[locale]/users/update/page.tsx +++ b/src/app/[locale]/users/update/page.tsx @@ -9,6 +9,7 @@ type Props = { readonly searchParams: { readonly [key: string]: string }; readonly params: { locale: string }; }; + export default function Page({ searchParams, params: { locale } }: Props) { const { id } = searchParams; unstable_setRequestLocale(locale); diff --git a/src/app/providers.tsx b/src/app/providers.tsx index 3f81cf2a..264a9f6f 100644 --- a/src/app/providers.tsx +++ b/src/app/providers.tsx @@ -5,16 +5,20 @@ import React from 'react'; import { NextUIProvider } from '@nextui-org/react'; import { ThemeProvider as NextThemesProvider } from 'next-themes'; import { Bounce, ToastContainer } from 'react-toastify'; + import '../features/shared/atoms/toast/toast.css'; +import { UserProvider } from '@/contexts/UserContext'; export function Providers({ children }: { children: React.ReactNode }) { return ( -
- - {children} -
+ +
+ + {children} +
+
); diff --git a/src/features/shared/actions/config.ts b/src/config/config.ts similarity index 100% rename from src/features/shared/actions/config.ts rename to src/config/config.ts diff --git a/src/contexts/UserContext.tsx b/src/contexts/UserContext.tsx new file mode 100644 index 00000000..7b7e28ac --- /dev/null +++ b/src/contexts/UserContext.tsx @@ -0,0 +1,32 @@ +'use client'; + +import React, { createContext, useState, useContext } from 'react'; +import { StaticImageData } from 'next/image'; + +import { images } from '@/features/shared/hooks/images'; + +interface UserContextType { + avatar: StaticImageData | string; + handleSetAvatar: (avatar: string) => void; +} + +const UserContext = createContext(undefined); + +export function UserProvider({ children }: { children: React.ReactNode }) { + const { Avatar } = images(); + const [avatar, setUserAvatar] = useState(Avatar); + + const handleSetAvatar = (avatar: string): void => { + setUserAvatar(avatar); + }; + + return {children}; +} + +export function useContextUser() { + const context = useContext(UserContext); + + if (!context) throw new Error('Context not defined'); + + return context; +} diff --git a/src/features/auth/forgot-password/actions/forgotPasswordAction.ts b/src/features/auth/forgot-password/actions/forgotPasswordAction.ts index fbe4e4e5..383db3b6 100644 --- a/src/features/auth/forgot-password/actions/forgotPasswordAction.ts +++ b/src/features/auth/forgot-password/actions/forgotPasswordAction.ts @@ -1,10 +1,10 @@ 'use server'; import { env } from '@/config/api'; -import { supabaseClientManager } from '@/lib/SupabaseClientManager'; +import { supabaseServerClientManager } from '@/lib/SupabaseServerClientManager'; export const handleRecoverPassword = async (username: string) => { - const supabase = supabaseClientManager.getPublicClient(); + const supabase = supabaseServerClientManager.getServerPublicClient(); const { error } = await supabase.auth.resetPasswordForEmail(username, { redirectTo: `${env.urlFront}/auth/update-password`, diff --git a/src/features/auth/login/actions/loginAction.ts b/src/features/auth/login/actions/loginAction.ts index ccdee655..aed794e3 100644 --- a/src/features/auth/login/actions/loginAction.ts +++ b/src/features/auth/login/actions/loginAction.ts @@ -4,12 +4,12 @@ import { redirect, RedirectType } from 'next/navigation'; import { getLang } from '@/features/shared/hooks/getLang'; -import { supabaseClientManager } from '@/lib/SupabaseClientManager'; +import { supabaseServerClientManager } from '@/lib/SupabaseServerClientManager'; import { ILoginForm } from '../interfaces/IloginForm'; export const handleSignIn = async (data: ILoginForm) => { - const supabase = supabaseClientManager.getPublicClient(); + const supabase = supabaseServerClientManager.getServerPublicClient(); const { lang } = getLang(); const { error } = await supabase.auth.signInWithPassword({ diff --git a/src/features/auth/recovery-code/actions/recoveryCodeAction.ts b/src/features/auth/recovery-code/actions/recoveryCodeAction.ts index 848736d5..49632d7c 100644 --- a/src/features/auth/recovery-code/actions/recoveryCodeAction.ts +++ b/src/features/auth/recovery-code/actions/recoveryCodeAction.ts @@ -4,12 +4,12 @@ import { redirect, RedirectType } from 'next/navigation'; import { getLang } from '@/features/shared/hooks/getLang'; -import { supabaseClientManager } from '@/lib/SupabaseClientManager'; +import { supabaseServerClientManager } from '@/lib/SupabaseServerClientManager'; import { IrecoveryCode } from '../interfaces/IrecoveryCode'; export const handleRecoveryCode = async (email: string, data: IrecoveryCode) => { - const supabase = supabaseClientManager.getPublicClient(); + const supabase = supabaseServerClientManager.getServerPublicClient(); const { lang } = getLang(); const { error } = await supabase.auth.verifyOtp({ email, token: data.code, type: 'recovery' }); diff --git a/src/features/auth/register/actions/registerAction.ts b/src/features/auth/register/actions/registerAction.ts index effa3dad..90c07efa 100644 --- a/src/features/auth/register/actions/registerAction.ts +++ b/src/features/auth/register/actions/registerAction.ts @@ -4,10 +4,10 @@ import { redirect, RedirectType } from 'next/navigation'; import { env } from '@/config/api'; import { IRegisterForm } from '@/features/auth/register/interfaces/IRegisterForm'; -import { supabaseClientManager } from '@/lib/SupabaseClientManager'; +import { supabaseServerClientManager } from '@/lib/SupabaseServerClientManager'; export const handleSignUp = async (props: IRegisterForm) => { - const supabase = supabaseClientManager.getPublicClient(); + const supabase = supabaseServerClientManager.getServerPublicClient(); const { error } = await supabase.auth.signUp({ email: props.email, diff --git a/src/features/auth/shared/actions/singOutAction.ts b/src/features/auth/shared/actions/singOutAction.ts index 73ed1168..614020b9 100644 --- a/src/features/auth/shared/actions/singOutAction.ts +++ b/src/features/auth/shared/actions/singOutAction.ts @@ -2,10 +2,10 @@ import { redirect, RedirectType } from 'next/navigation'; -import { supabaseClientManager } from '@/lib/SupabaseClientManager'; +import { supabaseServerClientManager } from '@/lib/SupabaseServerClientManager'; export const handleSignOut = async () => { - const supabase = supabaseClientManager.getPublicClient(); + const supabase = supabaseServerClientManager.getServerPublicClient(); const { error } = await supabase.auth.signOut(); diff --git a/src/features/auth/update-password/actions/updatePasswordAction.ts b/src/features/auth/update-password/actions/updatePasswordAction.ts index ee04f08e..e895ee2d 100644 --- a/src/features/auth/update-password/actions/updatePasswordAction.ts +++ b/src/features/auth/update-password/actions/updatePasswordAction.ts @@ -2,10 +2,10 @@ import { redirect, RedirectType } from 'next/navigation'; -import { supabaseClientManager } from '@/lib/SupabaseClientManager'; +import { supabaseServerClientManager } from '@/lib/SupabaseServerClientManager'; export const handleUpdatePassword = async (password: string, code: string) => { - const supabase = supabaseClientManager.getPublicClient(); + const supabase = supabaseServerClientManager.getServerPublicClient(); await supabase.auth.exchangeCodeForSession(code); diff --git a/src/features/items/actions/ItemAction.ts b/src/features/items/actions/ItemAction.ts index 5176f759..6adbcfc4 100644 --- a/src/features/items/actions/ItemAction.ts +++ b/src/features/items/actions/ItemAction.ts @@ -2,8 +2,8 @@ import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; +import { config } from '@/config/config'; import { ItemPayload, ItemsResponse } from '@/features/items/interfaces/itemsResponse'; -import { config } from '@/features/shared/actions/config'; import PayloadProps from '@/features/shared/interfaces/PayloadProps'; import HttpService from '@/service/HttpService'; import { HeadersContentType, IHttpParams } from '@/service/IHttpParams'; diff --git a/src/features/items/constants/paginationInitialParams.ts b/src/features/items/constants/paginationInitialParams.ts index ce09b30e..99631095 100644 --- a/src/features/items/constants/paginationInitialParams.ts +++ b/src/features/items/constants/paginationInitialParams.ts @@ -1,6 +1,6 @@ import { InitialPaginationParams } from '@/features/shared/interfaces/InitialPaginationParams'; export const paginationInitialParams: InitialPaginationParams = { - limit: '5', + limit: '6', offset: '0', }; diff --git a/src/features/items/organisms/List.tsx b/src/features/items/organisms/List.tsx index b4493c64..379437e4 100644 --- a/src/features/items/organisms/List.tsx +++ b/src/features/items/organisms/List.tsx @@ -87,12 +87,9 @@ export const List = ({ items, pagination }: Props) => { handleSearchType(); }, [handleSearchType]); - // TODO: Analizar si esto es realmente necesario useEffect(() => { - if (items.length === 0) { - handlePage(1); - } - }, [handlePage, items]); + handleReplaceURL(); + }, [currentPage]); return (
diff --git a/src/features/navbar/constants/dataNav.ts b/src/features/navbar/constants/dataNav.ts index 504fb133..4afa9fd4 100644 --- a/src/features/navbar/constants/dataNav.ts +++ b/src/features/navbar/constants/dataNav.ts @@ -1,20 +1,19 @@ import { icons } from '@/features/shared/hooks/icons'; import { images } from '@/features/shared/hooks/images'; -const { HomeIcon, ItemsIcon, CloseIcon, IconUser, IconSettings, IconLogOut, IconDropdown, UsersIcon } = icons(); -const { BgUser } = images(); +const { HomeIcon, ItemsIcon, CloseIcon, IconSettings, IconLogOut, IconDropdown, UsersIcon } = icons(); +const { Avatar } = images(); export const dataClose = { image: CloseIcon, }; export const dataUser = { - image: BgUser, + image: Avatar, username: 'babyoda@gmail.com', icon: IconDropdown, }; export const dataPerfil = [ { - icon: IconUser, description: 'perfil', path: '/profile', }, diff --git a/src/features/navbar/molecules/dropdown/DropdownUser.tsx b/src/features/navbar/molecules/dropdown/DropdownUser.tsx index 5e16c186..b54d263c 100644 --- a/src/features/navbar/molecules/dropdown/DropdownUser.tsx +++ b/src/features/navbar/molecules/dropdown/DropdownUser.tsx @@ -11,15 +11,15 @@ import { toast } from 'react-toastify'; import { locales } from '@/config'; import { handleSignOut } from '@/features/auth/shared/actions/singOutAction'; -import { User } from '@/features/navbar/organisms/NavbarTop'; import { icons } from '@/features/shared/hooks/icons'; +import { UserHasRole } from '@/features/shared/interfaces/UserHasRole'; import { AccordionComponent } from '@/features/shared/molecules/accordion/accordion'; import style from './dropdown-user.module.css'; type Props = { + avatar: string | StaticImageData; dataPerfil: { - icon: StaticImageData; description: string; path: string; }[]; @@ -37,7 +37,7 @@ type Props = { isUserDropdownOpen: boolean; handleDropdownUser: () => void; id?: string; - user?: User; + user?: UserHasRole; }; export const DropdownUser = (props: Props) => { @@ -64,12 +64,12 @@ export const DropdownUser = (props: Props) => {
@@ -84,12 +84,12 @@ export const DropdownUser = (props: Props) => {
{'Icon -

{props.user?.first_name ?? props.dataUser.username}

+

{props.user?.user_id.first_name ?? props.dataUser.username}

{props.dataPerfil.map(({ description, path }) => ( diff --git a/src/features/navbar/organisms/NavbarTop.tsx b/src/features/navbar/organisms/NavbarTop.tsx index fb3f36bf..2ad94798 100644 --- a/src/features/navbar/organisms/NavbarTop.tsx +++ b/src/features/navbar/organisms/NavbarTop.tsx @@ -1,28 +1,23 @@ 'use client'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; +import { useContextUser } from '@/contexts/UserContext'; import { dataLogin, dataPerfil, dataUser } from '@/features/navbar/constants/dataNav'; import { DropdownUser } from '@/features/navbar/molecules/dropdown/DropdownUser'; import { ChangeLanguage } from '@/features/shared/atoms/changeLanguage/changeLanguage'; import ThemeSwitcher from '@/features/shared/atoms/swich/ThemeSwitcher'; -import style from './navbar-top.module.css'; +import { UserHasRole } from '@/features/shared/interfaces/UserHasRole'; -export interface User { - phone: string | null; - email: string | null; - last_name: string | null; - first_name: string | null; - id: string; - image_id: string | null; -} +import style from './navbar-top.module.css'; type Props = { isPublic: boolean; - user?: User; + user?: UserHasRole; }; export const NavbarTop = (props: Props) => { + const { avatar, handleSetAvatar } = useContextUser(); const [isUserDropdownOpen, setIsUserDropdownOpen] = useState(false); const [isLangDropdownOpen, setIsLangDropdownOpen] = useState(false); const handleDropdownUser = () => { @@ -33,6 +28,15 @@ export const NavbarTop = (props: Props) => { setIsLangDropdownOpen(!isLangDropdownOpen); setIsUserDropdownOpen(false); }; + const handleSetAvatarUser = () => { + if (props.user?.user_id.image_id) { + handleSetAvatar(props.user.user_id.image_id); + } + }; + + useEffect(() => { + handleSetAvatarUser(); + }, []); return (
@@ -46,6 +50,7 @@ export const NavbarTop = (props: Props) => { <> { const user = props.isPublic ? undefined : await fetchUser(); + + if (user) { + const metadata = await handleGetFile(user.user_id.image_id); + user.user_id.image_id = metadata.path; + } + return ; }; diff --git a/src/features/profile/actions/ProfileAction.ts b/src/features/profile/actions/ProfileAction.ts index 9f970f85..4fde4cee 100644 --- a/src/features/profile/actions/ProfileAction.ts +++ b/src/features/profile/actions/ProfileAction.ts @@ -1,12 +1,12 @@ 'use server'; import { redirect, RedirectType } from 'next/navigation'; -import { updateRole } from '@/features/shared/actions/fetchUsers'; -import { SupabaseTable } from '@/features/shared/actions/supabaseTables'; -import { supabaseClientManager } from '@/lib/SupabaseClientManager'; +import { updateRole } from '@/features/shared/actions/roleActions'; +import { SupabaseTable } from '@/features/shared/constants/supabaseTables'; +import { supabaseServerClientManager } from '@/lib/SupabaseServerClientManager'; export const getSession = async () => { - const supabase = supabaseClientManager.getPublicClient(); + const supabase = supabaseServerClientManager.getServerPublicClient(); const { data, error } = await supabase.auth.getSession(); const user = data?.session?.user; @@ -24,13 +24,13 @@ export const getSession = async () => { type UpdatedUser = { first_name?: string; last_name?: string; - phone?: number; + phone?: string; role?: string; account_active?: boolean; }; export const updateUser = async (data: UpdatedUser, id: string) => { - const supabase = supabaseClientManager.getPublicClient(); + const supabase = supabaseServerClientManager.getServerPublicClient(); const phone = data.phone ? data.phone.toString() : null; const { error } = await supabase @@ -60,7 +60,7 @@ type UpdatedUserImage = { }; export const updateUserImage = async (props: UpdatedUserImage) => { - const supabase = supabaseClientManager.getPublicClient(); + const supabase = supabaseServerClientManager.getServerPublicClient(); const { error } = await supabase .from(SupabaseTable.PROFILES) diff --git a/src/features/profile/interfaces/profileResponse.ts b/src/features/profile/interfaces/profileResponse.ts index a62bb44c..a48a46fe 100644 --- a/src/features/profile/interfaces/profileResponse.ts +++ b/src/features/profile/interfaces/profileResponse.ts @@ -11,7 +11,7 @@ export interface ProfileResponse { export type ProfileType = { first_name?: string; last_name?: string; - phone?: number; + phone?: string; }; export interface ProfilePayload extends ProfileType {} diff --git a/src/features/profile/molecules/updateInfoUser/updateInfoUser.tsx b/src/features/profile/molecules/updateInfoUser/updateInfoUser.tsx index 808d4905..3bc6cecc 100644 --- a/src/features/profile/molecules/updateInfoUser/updateInfoUser.tsx +++ b/src/features/profile/molecules/updateInfoUser/updateInfoUser.tsx @@ -11,13 +11,13 @@ import { updateUser } from '@/features/profile/actions/ProfileAction'; import { ProfileType as IProfileType } from '@/features/profile/interfaces/profileResponse'; import style from '@/features/profile/molecules/updateInfoUser/updateInfoUser.module.css'; import { profileUpdateSchema } from '@/features/profile/validations/profileUpdateSchema'; -import { User } from '@/features/shared/actions/fetchUsers'; import { ButtonAuth } from '@/features/shared/atoms/button/ButtonAuth'; import { InputForm, InputType } from '@/features/shared/atoms/inputForm/InputForm'; +import { UserHasRole } from '@/features/shared/interfaces/UserHasRole'; interface Props { edit: boolean; - userProfile: User; + userProfile: UserHasRole; handleEditButton: () => void; phoneNumber: string; } @@ -35,7 +35,7 @@ export const UpdateInfoUser = ({ edit, userProfile, handleEditButton, phoneNumbe }); const onSubmit = handleSubmit(async (data: IProfileType) => { - await toast.promise(updateUser(data, userProfile.id), { + await toast.promise(updateUser(data, userProfile.user_id.id), { error: `${alert('error')}`, success: `${alert('success')}`, pending: `${alert('pending')}`, @@ -54,7 +54,7 @@ export const UpdateInfoUser = ({ edit, userProfile, handleEditButton, phoneNumbe className={style.infoUserEdit} input_type={InputType.SIMPLE} classNameError={style.inputError} - placeholder={userProfile.first_name ?? t('p_name')} + placeholder={userProfile.user_id.first_name ?? t('p_name')} disabled={!edit && true} /> @@ -67,19 +67,19 @@ export const UpdateInfoUser = ({ edit, userProfile, handleEditButton, phoneNumbe className={style.infoUserEdit} input_type={InputType.SIMPLE} classNameError={style.inputError} - placeholder={userProfile.last_name ?? t('p_lastname')} + placeholder={userProfile.user_id.last_name ?? t('p_lastname')} disabled={!edit && true} />

{t('p_email')}:

-

{userProfile.email ?? <>{t('p_email')}}

+

{userProfile.user_id.email ?? <>{t('p_email')}}

errors={errors} id={'phone'} name={'phone'} register={register} - type={'number'} + type={'text'} label={`${t('p_phone')}:`} className={style.infoUserEdit} input_type={InputType.SIMPLE} diff --git a/src/features/profile/molecules/userImage/UserImage.tsx b/src/features/profile/molecules/userImage/UserImage.tsx index 8be6edd5..2acc3ed6 100644 --- a/src/features/profile/molecules/userImage/UserImage.tsx +++ b/src/features/profile/molecules/userImage/UserImage.tsx @@ -1,7 +1,7 @@ -import React, { useState } from 'react'; +import React from 'react'; import { yupResolver } from '@hookform/resolvers/yup'; -import Image, { StaticImageData } from 'next/image'; +import Image from 'next/image'; import { useTranslations } from 'next-intl'; import { useTheme } from 'next-themes'; import { useForm } from 'react-hook-form'; @@ -9,13 +9,13 @@ import { toast } from 'react-toastify'; import IconPencil from '@/asset/images/pencil.svg'; import IconPencilWhite from '@/asset/images/pencilWhite.svg'; +import { useContextUser } from '@/contexts/UserContext'; import { updateUserImage } from '@/features/profile/actions/ProfileAction'; import style from '@/features/profile/molecules/userImage/user-image.module.css'; import { profileImageSchema } from '@/features/profile/validations/profileImageSchema'; -import { User } from '@/features/shared/actions/fetchUsers'; -import { handleGetFile, handleUploadFile } from '@/features/shared/actions/fileAction'; +import { handleGetFile, handleUploadFile } from '@/features/shared/actions/fileActions'; import { InputForm, InputType } from '@/features/shared/atoms/inputForm/InputForm'; -import { images } from '@/features/shared/hooks/images'; +import { UserHasRole } from '@/features/shared/interfaces/UserHasRole'; type IProfileForm = { file?: object | string | null; @@ -23,13 +23,12 @@ type IProfileForm = { }; interface Props { - userProfile: User; + userProfile: UserHasRole; edit: boolean; } export const UserImage = ({ userProfile, edit }: Props) => { - const { user } = images(); - const [imagePath, setImagePath] = useState(user); + const { avatar, handleSetAvatar } = useContextUser(); const alert = useTranslations('ToastUpdate'); const { theme } = useTheme(); const pencilToggle = theme === 'dark' ? IconPencilWhite : IconPencil; @@ -58,11 +57,11 @@ export const UserImage = ({ userProfile, edit }: Props) => { file.append('file', event.target.files[0]); const fileMetadata = await handleUploadFile(file); const data = { - id: userProfile.id, - first_name: userProfile.first_name, - last_name: userProfile.last_name, - phone: userProfile.phone, - email: userProfile.email, + id: userProfile.user_id.id, + first_name: userProfile.user_id.first_name, + last_name: userProfile.user_id.last_name, + phone: userProfile.user_id.phone, + email: userProfile.user_id.email, image_id: fileMetadata.id, }; @@ -75,7 +74,7 @@ export const UserImage = ({ userProfile, edit }: Props) => { }); const image = await handleGetFile(fileMetadata.id); - setImagePath(image.path); + handleSetAvatar(image.path); } } }; @@ -83,7 +82,7 @@ export const UserImage = ({ userProfile, edit }: Props) => { return ( <>
- {'user'} + {'user'} {edit && (