No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Convierte tus certificados en títulos universitarios en USA

Antes: $249

Currency
$209

Paga en 4 cuotas sin intereses

Paga en 4 cuotas sin intereses
Suscríbete

Termina en:

18 Días
4 Hrs
18 Min
8 Seg

Reto: roles complejos

17/30
Recursos

Aportes 25

Preguntas 3

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Saludos, compañeros! (0,0)/

Aquí les comparto mi progreso del curso y los retos aplicados. Si alguien le sirve de referencia o si alguien gusta proporcionar algo de feedback, ya sea en buenas practicas o sugerencias, se agradece y mucho.

Repo.
GitHub page.

Advierto que sólo trabajé funcionalidad, por lo que no esperen un trabajo estético xD.
Los usuarios admin del sistema son: Irisval, RetaxMaster, freddier, alex. Los usuarios “normales” son juandc, nameless.

Hardcoded data.

  • Para manejar los datos “hardcodeados” opté por crear archivos para la información del usuario (user.js), blogs (blogdata.js) y los roles de usuario (roles.js). Todos ubicados en src/data/.

  • Inspirandome en aportaciones dadas por compañeros en clases anteriores, removí la lista de Admins y agregué nuevas propiedades como phonenumber, description y roles, este último siendo un objeto el cual guarda un arreglo de roles.

  • Para el manejo de roles implementé el uso de constantes. En este caso sólo creé dos roles: ADMIN (admin) y USER (user).

  • Agregué a los blogs una propeidad comments el cual, obviamente, guarda un arreglo de comentarios.

  • La estructura de un comentario es la de un objeto con las propiedades content y author.

Services.

  • Las funcionalidades de Auth del archivo auth.js visto en el curso las trabajé como un servicio. Por lo que creé la carpeta src/services/.

  • Por comodidad creé un servicio user.js. Esto con el fin de manipular la data del usuario y desarrollar funciones acorde a este. Buscando mantener auth.js lo más posible a sólo cosas relacionadas a funciones de autentificación.

  • Creé un servicio que funciona como la BD de usuarios. usersDB.js. Esto con el fin de servir de apoyo para las funciones de actualización de usuarios.

  • Para la manipulación de los blogs opté por crear un servicio al igual que auth: blog.js. Siendo este una copia del auth.js ya que utilizo la mismas funciones o características. Un provider, un useBlog y BlogRoute (ojo en este último).

  • BlogRoute. Durante el proceso me di cuenta que si accedía a un blog que no existe la app truena. Por lo que redirecciono al usuario en caso de que no exista el blog que indica en la url.

  • BlogProvider/BlogContext. Además de gestionar los blogs, también sus comentarios. Dejé todos los métodos dentro de este servicio, aunque otra opción sería crear un servicio de comentarios para manipularlos desde ahí utilizando useBlog. Algo parecido a useUser.

Gestión de blogs.

  • Habilité las funciones de eliminar blogs. Permitiendo dicha función unicamente al propietario del blog o los usuarios admin.

  • Añadí un formulario para la creación de nuevos blogs (sólo para usuarios con una sesión iniciada).

  • Añadí una sección de comentarior en el detalle de un blog y la función de agregar un nuevo comentario (sólo para usuarios con una sesión iniciada).

  • Al igual que los blogs, los usuarios pueden borrar sus comentarios. Pero un usuario admin puede cualquier comentario.

UX login.

  • En caso de no tener una sesión iniciada, la vista blog cuenta con un botón para dirigir al usuario a la vista de login. Una vez iniciada la sesión, el usuario volverá a la vista de blog.

  • La funcionalidad anterior también fue implementada desde la sección de comentarios de un blog.

Editar perfil de usuario.

  • Además de agregar más datos para el perfil de los usuarios, también agregué la posibilidad de cambiar estos datos.

  • Únicamente permito actualizar el número de teléfono y la descripción del usuario.

  • username no es un campo válido a modificar (al menos en este proyecto) debido a que funciona como el identificador del usuario y los blogs y comentarios los relaciono a partir de este.

  • Es posible ver el perfil de otros usuarios ingresando su username en la ruta: e.j. profile/juandc.

  • Los usuarios admin pueden modificar el perfil de otros usuarios.

Posibles mejoras.

  • Agregar estilos (por supuesto xD).

  • Implementar función de Editar (blogs y comentarios).

  • Guardar comentarios con algún identificador y/o datetime para facilitar la gestión de estos.

  • El propietario del blog sea capaz de eliminar u ocultar comentarios de sus blogs.

  • Uso de algún ID para los usuarios (number, hash, etc.).

  • Editar roles de usuario.

  • Ajustes en los servicios.

Lo logré!

Después de muchos intentos y muchas pruebas, pude hacerlo sin buscar la respuesta en internet.
.
Lo primero que hice fue cambiar la forma en la que se logeaban los usuarios. Agregue la clase User para que todos tengan la misma estructura.

auth.js

class User {
    constructor(username) {
        this.username = username
        this.roles = {}
    }
}

export function AuthProvider({ children }) {
    const navigate = useNavigate()
    const [user, setUser] = useState(null)

    const login = ({ username, locationAfterLogin }) => {
        const existingUser = users.find(user => user.username === username)

        if (existingUser) setUser(existingUser)
        else {
            const newUser = new User(username)
            users.push(newUser)
            setUser(newUser)
        }

        navigate(locationAfterLogin)
    }

    const logout = () => {
        setUser(null)
    }

    const auth = { user, users, login, logout }
    return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>
}

Esa fue la parte fácil. Lo segundo fue usar useParams() en ProfilePage. Cambie la ruta de ‘/profile’ a ‘/profile/:username’, luego hice comprobaciones para saber si se trataba de la cuenta del usuario logeado o no.

ProfilePage.js

export function ProfilePage() {
    const { user, users } = useAuth()
    const { username } = useParams()
    let isAuthor

    const userProfile = users.find(user => user.username === username)
    if (!userProfile) return <p>sorry! {username} isn't a logged user.</p>

    if (userProfile === user) isAuthor = true

    if (isAuthor)
        return (
            <>
                <h1>Hello, {username}</h1>
                <button>Edit your profile here!</button>
            </>
        )
    return <h1>Profile of {username}</h1>
}

Surgió el problema que más me costó resolver: el link en Menu redirigia a ‘/profile’, pero esa ruta ya no existía. Probé muchas cosas.

  1. Cambiar la ruta a ‘/profile/${user.username}’, pero el menu no se re-renderizaba al cambiar el user, resultando en que siempre lleve al primer usuario logeado.
  2. Agregar un Navigate a ProfilePage si username no existía, pero claro, la ruta ‘/profile’ en ningún momento llevaba a mi componente.
  3. Usar Outlet (ni yo sabía como) o renderProps (no me acuerdo por qué pensé que eso tendría sentido)
  4. Agregar Navigate to=’/profile/{user.username}’/> en App, pero App no tiene acceso a user.
    Finalmente, agregué un componente ProfileRedirect en ProfilePage.js

ProfilePage.js

export function ProfileRedirect() {
    const { user } = useAuth()
    if (user) return <Navigate to={`/profile/${user.username}`} />
    return <NotFound />
}

App.js

<Route
                            path="/profile/:username"
                            element={<ProfilePage />}
                        />
                        <Route path="/login" element={<LoginPage />} />

Siento que no quería llegar a esta solución, no me parece la más elegante, pero es la que conseguí después de muchos intentos. Y funciona perfecto!
.
Por último, cualquier sugerencia va a ser agradecida. Gracias por leer 😄

Mi solución

Cuando Juan mencionó todo el reto, por mi mente pasó un “Aaaaihhggg… por dónde empiezo…”
.
Les describiré cómo se fue creando mi solución en mi cabeza:
.

Cambiar estructura de profiles

Antes solo podíamos mirar nuestro perfil, ahora tenemos que hacer que cualquier perfil pueda ver cualquier otro perfil.
La ruta “/profile” contiene un input con un buscador de perfiles (hice un array con perfiles hardcodeados). La ruta “/profile/:username” contiene la vista del perfil del usuario.

<Route path="/profile/" element={<SearchProfilePage />} />
<Route path="/profile/:username" element={<ProfilePage />} />

.

Aplicar lógica de búsqueda y navegación

En el buscador de perfiles, verifico si el username ingresado en el input existe; si no existe, lanzo un alert(), si sí existe, navego hacia la ruta “/profile/${username}”

function SearchUserPage() {
	const navigate = useNavigate();
	const [username, setUsername] = useState("");

	const searchUser = (ev) => {
		ev.preventDefault();

		if (!users.some((user) => user.username === username)) {
			alert("This user does not exist. Try another user.");
			return;
		}

		navigate(`/profile/${username}`);
	};

	return (
		<>
			<h2>Search User</h2>

			<form onSubmit={searchUser}>
				<label htmlFor="username">Type an username</label>
				<input
					type="text"
					name="username"
					id="username"
					value={username}
					onChange={(ev) => setUsername(ev.target.value)}
				/>
				<button type="submit">Search</button>
			</form>
		</>
	);
}

.

Renderizar y Autorización en Profile Page

En Profile Page, recojo el user autenticado y el user enviado por la URL.
Si los dos son el mismo user, tiene permiso para editar el perfil.
Si el user autenticado tiene el rol de admin, puede editar el perfil.

function ProfilePage() {
	const { user } = useAuth();
	const params = useParams();

	const data = users.find((user) => user.username === params.username);

	const canEditProfile = () => {
		if (user.role.name === "admin") return true;
		if (user.username === data.username) return true;

		return false;
	};

	return (
		<>
			<h1>ProfilePage</h1>

			<h2>{data.username}</h2>
			<img src={data.img.src} alt={data.img.alt} width="400" />
			<p>{data.bio}</p>
			{canEditProfile() && <button>Edit Profile</button>}
		</>
	);
}

.
.

Mi filosofía para este reto

Lo que hago es simular una base de datos teniendo un array de usuarios en un archivo aparte, y en cada búsqueda o autenticación, lo consulto. Y dentro de cada componente hago la lógica necesaria trabajando los datos que tengo: array de usuarios y user autenticado (o no autenticado)

Reto completado , muy muyyyy retador pero se logro * Repo : <https://github.com/Ulternae/Blogpost/> * Page : <https://ulternae.github.io/Blogpost/> ![](https://static.platzi.com/media/user_upload/image-cad8625a-e7a9-4326-8eb1-1124c5da88df.jpg) Trabaje con Tailwind un diseño responsivo * Cuenta con usuarios con distintos permisos como Admin, Editor, Tester, User, solo tienes que seleccionar alguno, ya tiene la info predefinida, si quieres crear tu usuario propio lo puedes hacer mediante el storage ya que todo es manejado con localStorageAsincrono con un tiempo de retraso de 1segundo para simular traer la data de la base de datos * De igual forma los blogs se manejan mediante el LocalStorage, manejo datos por default que se insertan cuando el localStorage es nullo por tanto si rompes la app pones en la consola LocalStorage.clear() la info se resetea * Solo los tester y admin pueden ser capaces de crear blogs * Los editor pueden editar cualquier blog * Los usuarios cuyo blogs son propios puedes editarlo * Cualquier usuario logeado puede crear sus comentarios en cualquier blog, únicamente su creador los puede editar * El admin puede editar o borrar el blog * El usuario que coincide con el perfil de el puede editarlo, para editarlo necesita la contraseña, no puedes cambiar el rol * El admin puede editarlo y eliminarlo, al editar ya tiene la contraseña dada y puede cambiar los permiso, un admin puede hacer a todos admin * Toda la data sincronizada Posibles mejoras * Que se pueda crear cualquier usuario y verificar que no colapse con usuarios existentes, y en el caso que quiera cambiar contraseña lo pueda hacer Cualquier feedback es bien recibido, os invito a comentan si tienten dudas de como realice algo o como lo podría mejorar , comenten, saludos compañeros de Platzi

Hola amigos, les comparto el deploy de mi app y el repositorio por si a alguien le sirve

Github Pages - Deploy

Repositorio

La lista de admins son [ JuanDC - Freddier - Retaxmaster ]

La aplicación tiene persistencia en local storage de los blogs y usuarios, puedes crear tus propios blogs, puedes eliminar tus blogs pero si inicias sesión con otro user ya no podrás hacerlo, a no ser que sean un administrador, un admin puede hacer todo

Reto 1 😰 Reto 2 😃 Reto 3 😰

No lo logré. Comparto una forma de no hacerlo. Yo creé un array aparte para simular un “backend” y creé las rutas de la misma forma que en Blogpage y BlogPost

//Este es el "backend"
const publicData = [];
publicData.push({
    name: 'Jhonny',
    slug_Url: '@jhonny-c',
    nickname: '@jhonny-c',
});


....

Utilize el contexto Para compartir el “backend”. También creé un método signUp para crear una cuenta.

function AuthProvider({ children }) {
const [newUser, setNewUser] = React.useState(publicData);
...

const signup = ({ username, nickname }) => {
	un  objeto que representa a un nuevo usuario
       const newSignUp = {
            name: username,
            slug_Url: '@' + nickname,
            nickname: '@' + nickname,
        }
        //Crear una copia del "backend"
        const createUser = [...newUser]
	//concatenar el nuevo usuario al "backend"
        createUser.push(newSignUp);
	//cambiar el "backend" para toda la app
        setNewUser(createUser);

//esta parte es para comprobar si el usuario existe
       
        const findUser = newUser.find(user => user.name === username);
        //el hook useLocation
        const from = location.state?.from?.pathname || -1;
//si el usuario no existe 
        findUser === undefined ?
//crearlo
        setUser({ name: username, nickname: nickname, role: roles.visitor })
//si existe es auth.user
        : setUser(findUser)
      //el hook useLocation
        navigate(from , { replace : true });

    }

 const auth = {  
        user, 
        ...
        newUser,
    }; 

La sub-ruta de ProfilePage es PublicProfile asi se ve desde App.js

function App () {
return(
...
<Route path="/profile"
         element={
        <ProfilePage/>
        }
        >

        <Route path=":slug" 
          element={
          <PublicProfile/>
          }
        />
      </Route>
...
)
}

Asi queda PublicProfile.js

function PublicProfile() {
   
    const { slug } = useParams();

   
    const { user, newUser}= useAuth();
  
   
    const profile =  newUser?.filter(person => person.slug_Url === slug);
  
    const verified = user === profile.name;


    return(
        <>
        {profile && !verified && profile.map( p => <ProfilesPub 
        props={p}
        />)
        }
        {profile && verified && profile.map( p => <Profiles 
        props={p}
        />)}
        </>
    );
}

//este componente es para cuando hace login  y le permite modificar el perfil
function Profiles({props}) {
 
    return(
        <>
        <div>
            <h1>Profile</h1>
            <h3>{props.name}</h3><button>editar</button>
            <div></div><button>editar</button>
            <div>Nickname: {props.nickname}</div><button>editar</button>
        </div>
        </>
    );
}
//este componente solo muestra el perfil público y no permite modificaciones.
function ProfilesPub({ props }) {
 
    return(
        <>
        <div>
            <h1>Profile</h1>
            <h3>{props.name}</h3>
            <div></div>
            <div>Nickname: {props.nickname}</div>
        </div>
        </>
    );
}

Reitero esta es una forma que no funciona. Desde LoginPage o SignUpPage se crea la sub-ruta directamente. Además, la sub-ruta solo es disponible después de hacer logout.

Mi solución: 1\) Para crear las rutas dinámicas, es básicamente mirar lo que hicimos anteriormente con el contenido de los blogs. Primero crear un componente donde renderizaremos las paginas dinámicas. y con ello hacer la modificación permitente en nuestro Router ```js <Route path="/profile" element={ <AuthRoute> <Profile /> </AuthRoute> } > <Route path=":users" element={<ProfileUser />} /> </Route> ```2) Modificar el valor de nuestra ruta profile que tenemos en menu.jsx ```js { to: `/profile/${auth.user?.userName}`, text: 'Profile', private: true, } ```Con esto ya logramos el tener paginas dinámicas al ir a profile según el usuario con el que ingresemos en login. 3\) ahora es hora de dar cierta personalización en cada pagina, para ello elegí darle mas información a cada usuario, este array decidí agregarlo un estado, pero al tener mas de 3 estados en auth.jsx opte por crear un estado compuesto para tener un mejor manejo: ```js const userList = [ { user: 'SrPizza', nickname: 'Sr_Pizza', location: 'Bogotá, Col', role: 'admin' }, { user: 'Percy', nickname: 'Perrito_Percy', location: 'Bogotá, Col', role: 'admin' }, { user: 'Nicolas', nickname: 'XnicolasG', location: 'NY, US', role: 'poster' } ]; ``` ```js const [authState, setAuthState] = useState({ user: null, dataBlog: Blogdata, prevPath: null, userList: userList }) ```con esto podemos renderizar la información en cada perfil según el usuario primero ubicando los datos en el array segun nuestro slug en este caso lo llame 'users' ```js const { users } = useParams(); let Users = auth.userList.find(us => us.user === users) ```con esto ya podemos renderizar la información de nuestro usuario especifico dandole cierta diferencia de si el usuario se encuentra registrado en nuestro array o no ```js return ( <section> { Users ? ( <>

{Users.user} - ({Users.role})

{Users.nickname}

{Users.location}

) : (

{auth.user.userName} - (Visitor)

) } </section> ) ```4) Por último decidí que se pueda editar el nickname, pero solamente si es la persona es dueña del perfil, o si su rol es admin. Lo primero es dictar un condicional inicialmente creando una variable que use para la condición ```js const canChangeName = Users || auth.userList.some((us) => us.user === auth.user?.userName && us.role === 'admin') ```una vez creada ya podemos generar la condición en la cual genere un pequeño formulario con un input y un boton: ```js return ( <section> { Users ? ( <>

{Users.user} - ({Users.role})

{Users.nickname}

{Users.location}

{ canChangeName && ( <form onSubmit={changeName}> <label htmlFor="Nuevo nombre"></label> <input value={newName} onChange={(e)=>setNewName(e.target.value)} type="text" /> <button type='submit'>Cambiar nombre</button> </form> ) } ) : (

{auth.user.userName} - (Visitor)

) } </section> ) } ```5) para generar la acción de editar el nickname y vincularlo al formulario lo hice de esta manera: ```js const changeName = (e) =>{ e.preventDefault(); const profileUser = Users.user; const findUser = auth.userList.find((us)=> us.user === profileUser); if (findUser) { findUser.nickname = newName; auth.setAuthState((prevState)=>({ ...prevState, userList:Array.isArray(prevState.userList) ? [...prevState.userList] : [] })) } } ```
Complete los 3 retos, la parte a la que agregé mayor funcionalidad fue al blog, ya que se puede editar y eliminar comentarios, el titulo y el contenido del BlogPost. Para lograr todas las validaciones de los roles y las acciones permitidas y no permitidas en el blog use un nuevo custom hook que hice yo mismo. Espero les agrade mi solución. No creo que sea la forma más optima de hacerlo, pero me sirvió para reforzar mas cosas sobre el estado, la composición de componentes, useReducer, y los closures.import React, { useReducer } from "react"; const initialState = {    *//role:"visitor",*    read:true,    selfWrite:false,    selfDelete:false,    elseWrite:false,    elseDelete:false,}; *//Action Types*const actionTypes = {    admin: 'admin',    editor: 'editor',    customer: 'customer',    visitor: 'visitor',} const reducerObject = (*state*, *action*) => {  const actions = {        \[actionTypes.admin]: {        ...*state*,        userName:*action*.payload,        role:actionTypes.admin,        selfWrite:true,        selfDelete:true,        elseWrite:true,        elseDelete:true,    },    \[actionTypes.editor]: {        ...*state*,        userName:*action*.payload,        role:actionTypes.editor,        selfWrite:true,        selfDelete:true,        elseWrite:false,        elseDelete:false,    },     \[actionTypes.customer]: {        ...*state*,        userName:*action*.payload,        role:actionTypes.customer,        selfWrite:true,        selfDelete:true,        elseWrite:false,        elseDelete:false,    },   };  return actions\[*action*.type] || initialState;}; export function useAuthReducer() {   const \[state, dispatch] = useReducer(reducerObject, initialState);    *//Action creators*  const onAdmin = (*userName*) => {dispatch({type: actionTypes.admin, payload:*userName*});}  const onEditor = (*userName*) => {dispatch({type: actionTypes.editor, payload:*userName*});}  const onCustomer = (*userName*) => {dispatch({type: actionTypes.customer, payload:*userName*});}  const onvisitor = () => {dispatch(initialState);}    *//   const onWrite = (eventValue) => {*    *//       dispatch({type: actionTypes.write, payload:eventValue});*    *//   }*    *//create a switch expression executing the 3 above functions according to the userRole parameter*    const dispatchUser = (*user*)=>{      if(!!*user*){        switch (*user*.userRole) {        case 'admin':            onAdmin(*user*.userName);            break;        case 'editor':            onEditor(*user*.userName);            break;        case 'customer':            onCustomer(*user*.userName);            break;        default:            break;      }}else{        onvisitor();      }    }    return \[state, dispatchUser]     *//   const onDelete = (eventValue) => {*} Custom hook, useBlogContent,js: ```js import React from 'react' export const useBlogContent = ({ contentToChange, actions, setBlogdataLocal }) => { const { onEdit, onDelete } = actions const [edit, setEdit] = React.useState(false); const [inputContent, setInputContent] = React.useState(contentToChange); const [loading, setLoading] = React.useState(false); const handleEdit = () => { setEdit(true); }; const handleSave = () => { setLoading(true); setInputContent(inputContent); setBlogdataLocal(onEdit); setEdit(false); setLoading(false); }; const handleDelete = () => { setLoading(true); setBlogdataLocal(onDelete); setLoading(false); }; const handleCancelEdit = () => { setEdit(false); const currentValue = inputContent; setInputContent(currentValue); }; return { edit, inputContent, loading, setInputContent, handleEdit, handleSave, handleDelete, handleCancelEdit, }; }; ```Componente BlogPost (Composicion de componente, permisos, usa el customhook useBlogContent): ```js import React from 'react' import { useParams, useNavigate } from 'react-router-dom' //import { blogdata } from './blogData' import { useAuth } from './auth' import Comments from './Comments/Comments' import Comment from './Comments/Comment' import RequestButton from './Buttons/RequestButton' import { useBlogContent } from './useBlogContent' function BlogPost({blogDataStates}) { const [blogdataLocal, setBlogdataLocal] = blogDataStates const navigate = useNavigate() const auth = useAuth() const { user } = auth const { slug } = useParams() const { title, content, author, comments } = blogdataLocal.find(post => post.slug === slug) const returnToBlog = () => { navigate('/blog') } const updateActions = { 'EDIT': 'EDIT', } const updateTitlePost = (action, inputContent) => ( blogdataLocal.map(post => { if (post.slug === slug) { if(action === updateActions.EDIT){ return { ...post, title: inputContent }; } } return post; }) ) const updateContentPost = (action, inputContent) => ( blogdataLocal.map(post => { if (post.slug === slug) { if(action === updateActions.EDIT){ return { ...post, content: inputContent }; } } return post; }) ) const deletePost = () => { const updatedBlogData = blogdataLocal.filter(post => post.slug !== slug); setBlogdataLocal(updatedBlogData); navigate('/blog') } const { edit:editTitle, inputContent: inputContentTitle, loading: loadingTitle, setInputContent: setInputContentTitle, handleEdit: handleEditTitle, handleCancelEdit: handleCancelEditTitle, handleSave: handleSaveTitle, } = useBlogContent({ contentToChange: title, actions: { onEdit: () => updateTitlePost(updateActions.EDIT, inputContentTitle), }, setBlogdataLocal, }); const { edit:editContent, inputContent: inputContentPostContent, loading: loadingContent, setInputContent: setInputContentPostContent, handleEdit: handleEditContent, handleCancelEdit: handleCancelEditContent, handleSave: handleSaveContent, } = useBlogContent({ contentToChange: content, actions: { onEdit: () => updateContentPost(updateActions.EDIT, inputContentPostContent), }, setBlogdataLocal, }); return ( <> <header> {!editTitle &&

{title}

} {editTitle && ( <input type="text" value={inputContentTitle} onChange={(e) => setInputContentTitle(e.target.value)} /> )} {!editTitle && (
{(user?.elseWrite || user?.userName === author) && ( <button onClick={handleEditTitle}>Editar título</button> )}
)} {editTitle && (
<RequestButton loading={loadingTitle} requestType={"EDIT"} onClick={handleSaveTitle} > Guardar </RequestButton> <button onClick={handleCancelEditTitle}>Cancelar</button>
)} </header>
<button onClick={returnToBlog}>Volver al blog</button>

{author}

{!editContent &&

{content}

} {editContent && ( <textarea value={inputContentPostContent} onChange={(e) => setInputContentPostContent(e.target.value)} /> )} {!editContent && ( <> {user?.elseWrite && <button onClick={handleEditContent}>Editar Contenido</button>} {user?.userName === author && (!user?.elseWrite || !user?.elseDelete) && <button onClick={handleEditContent}>Editar Contenido</button> } )} {editContent && (
<RequestButton loading={loadingContent} requestType={"EDIT"} onClick={handleSaveContent} > Guardar </RequestButton> <button onClick={handleCancelEditContent}>Cancelar</button>
)}
{user?.elseDelete && <RequestButton requestType={"DELETE"} onClick={deletePost}>Borrar Post</RequestButton>} {user?.userName === author && (!user?.elseWrite || !user?.elseDelete) && <RequestButton requestType={"DELETE"} onClick={deletePost}>Borrar Post</RequestButton> }
<Comments> {comments.map(({ idUser, content }, index) => ( <Comment key={index} idUser={idUser} content={content} slug={slug} setBlogdataLocal={setBlogdataLocal} blogdataLocal={blogdataLocal} /> ))} </Comments> {/*crea */} ); } export default BlogPost ```Componente Comment (Composicion de componente, permisos, usa el customhook useBlogContent): ```js import React from 'react' import { users, useAuth } from '../auth' import RequestButton from '../Buttons/RequestButton'; import { useBlogContent } from '../useBlogContent.js'; function Comment({idUser, content, slug, blogdataLocal, setBlogdataLocal}) { const author = findUser(idUser) const { user } = useAuth() const updateActions = { 'EDIT': 'EDIT', 'DELETE': 'DELETE' } const updatedBlogData = (action, inputContent) => ( blogdataLocal.map(post => { if (post.slug === slug) { let updatedComments; if(action === updateActions.DELETE){ updatedComments = post.comments.filter(comment => comment.idUser !== idUser); } else if(action === updateActions.EDIT){ updatedComments = post.comments.map(comment => { if(comment.idUser === idUser){ return { ...comment, content: inputContent }; } return comment; }); } return { ...post, comments: updatedComments }; } return post; }) ) const { edit, inputContent, loading, setInputContent, handleEdit, handleCancelEdit, handleDelete: handleDeleteComment, handleSave, } = useBlogContent({ contentToChange: content, actions: { onEdit:()=> updatedBlogData(updateActions.EDIT,inputContent), onDelete:()=> updatedBlogData(updateActions.DELETE, inputContent), }, setBlogdataLocal, }); return ( <article> <h5>{author.userName}</h5>
{!edit &&

{content}

} {edit && <textarea value={inputContent} onChange={(e) => setInputContent(e.target.value)} />}
{!edit &&
{user?.elseWrite && <button onClick={handleEdit}>Editar</button>} {user?.elseDelete && <RequestButton requestType={'DELETE'} onClick={handleDeleteComment} loading={loading}>Borrar</RequestButton>} {user?.userName === author.userName && (!user?.elseWrite||!user?.elseDelete) && ( <> <button onClick={handleEdit}>Editar</button> <RequestButton requestType={'DELETE'} loading={loading} onClick={handleDeleteComment}>Borrar</RequestButton> )}
} { edit &&
<RequestButton loading={loading} requestType={'EDIT'} onClick={handleSave}>Guardar</RequestButton> <button onClick={handleCancelEdit}>Cancelar</button>
} </article> ); } //a function that iterates over a list of objects and return the object that have the same userName that i am looking for export function findUser(idUser){ let user = users.find(user => user.id === idUser) user = user ? user: {userName: 'Anónimo'} return user } export default Comment ``` auth.js (aqui resuelvo el segundo y tercer reto, además uso el reducer para permitir la escalabilidad de mas roles): ```js import React from 'react' import { useNavigate, Navigate, useLocation } from 'react-router-dom'; import { useAuthReducer } from './AuthReducer' const AuthContext = React.createContext(); const roles = { admin: 'admin', editor: 'editor', customer: 'customer', visitor: 'visitor', }; export const users = [ { id: 1, userName: "Andres", role: roles.admin, }, { id: 2, userName: "Felipe", role: roles.editor, }, { id: 3, userName: "Pedro", role: roles.customer, }, ]; export function AuthProvider({children}) { //const [user, setUser] = useState(null) const [user, dispatchUser] = useAuthReducer(); const navigate = useNavigate() const login = ({ userName, callback }) => { //revisar si el usuario existe o lo crea como visitante const userFound = users.find((usu) => usu.userName === userName); if (userFound !== undefined) { dispatchUser({ userName: userFound.userName, userRole: userFound.role })}else{ dispatchUser(null); } if (callback){ callback(); } else{ navigate("/profile"); } } const logout = () => { dispatchUser(null); navigate('/') } const auth = { user, login, logout} return ( <AuthContext.Provider value={auth}> {children} </AuthContext.Provider> ) } export function useAuth() { const auth = React.useContext(AuthContext) return auth } export function RequireAuth(props) { const auth = useAuth(); let location = useLocation(); if (!auth.user.role) { return <Navigate to="/login" state={{ from: location }} replace/> } return props.children } ```AuthReducer.js: ```js import React, { useReducer } from "react"; const initialState = { //role:"visitor", read:true, selfWrite:false, selfDelete:false, elseWrite:false, elseDelete:false, }; //Action Types const actionTypes = { admin: 'admin', editor: 'editor', customer: 'customer', visitor: 'visitor', } const reducerObject = (state, action) => { const actions = { [actionTypes.admin]: { ...state, userName:action.payload, role:actionTypes.admin, selfWrite:true, selfDelete:true, elseWrite:true, elseDelete:true, }, [actionTypes.editor]: { ...state, userName:action.payload, role:actionTypes.editor, selfWrite:true, selfDelete:true, elseWrite:false, elseDelete:false, }, [actionTypes.customer]: { ...state, userName:action.payload, role:actionTypes.customer, selfWrite:true, selfDelete:true, elseWrite:false, elseDelete:false, }, }; return actions[action.type] || initialState; }; export function useAuthReducer() { const [state, dispatch] = useReducer(reducerObject, initialState); //Action creators const onAdmin = (userName) => {dispatch({type: actionTypes.admin, payload:userName});} const onEditor = (userName) => {dispatch({type: actionTypes.editor, payload:userName});} const onCustomer = (userName) => {dispatch({type: actionTypes.customer, payload:userName});} const onvisitor = () => {dispatch(initialState);} //a switch expression executing the 3 above functions according to the userRole parameter const dispatchUser = (user)=>{ if(!!user){ switch (user.userRole) { case 'admin': onAdmin(user.userName); break; case 'editor': onEditor(user.userName); break; case 'customer': onCustomer(user.userName); break; default: break; }}else{ onvisitor(); } } return [state, dispatchUser] // const onDelete = (eventValue) => { } ```

Buenas, me demore un poco pero lo logre.
Le puse algo de amor con bootstrap ❤️ My Blog

Hola a comunidad, les dejo mi código del proyecto en typescript.
Tome varias ideas interesantes de otros comentarios, espero el mio tambien les sea de ayuda:

import { NavLink } from 'react-router-dom'
import { useAuth } from '../hooks/useAuth';

const Menu = () => {

    const {  user } = useAuth();

    return(
        <nav>
            <ul>
                {
                    routes.map(route=>{

                        if(route.to.includes('/profile')) route.to = `/profile/${user?.username.toLowerCase()}`

                        if(route.private && !user) return null

                        if(route.to === '/login' && user) return null

                        return (
                            <li key={route.id}>
                                <NavLink 
                                    style={({ isActive })=>({
                                        color: isActive? 'red': 'blue'}
                                    )}
                                    to={route.to}
                                >
                                    {route.text}
                                </NavLink>  
                            </li>
                        )
                    })
                }
            </ul>
        </nav>
    )
}

interface IRoute {
    id: number,
    to: string,
    text: string,
    private: boolean,
}

const routes:IRoute[] = [
    {
        id: 1,
        to: '/',
        text: 'Home',
        private: false,
    },
    {
        id: 2,
        to: '/blog',
        text: 'Blog',
        private: false,
    },
    {
        id: 3,
        to: '/profile',
        text: 'Profile',
        private: true,
    },
    {
        id: 4,
        to: '/login',
        text: 'Login',
        private: false,
    },
    {
        id: 5,
        to: '/logout',
        text: 'Logout',
        private: true,
    }
];



export { Menu }
function App() {

  return (
    <>
      <HashRouter>
        <UsersDBProvider>
          <AuthProvider>
            <BlogProvider>
            <Menu />

            <Routes>
              <Route path='/' element={<HomePage />} />

              <Route path='/blog' element={<BlogPage />}>
                <Route path=':slug' element={<BlogPost />} />
              </Route>
              
              <Route path='/login' element={<LoginPage />} />
              <Route path='/logout' element={
                <AuthRoute>
                  <LogoutPage />
                </AuthRoute>
              } />

              <Route path='/profile/:username' element={<ProfilePage />} />

              <Route path='*'  element={<p>Not found</p>} />
            </Routes>
            </BlogProvider>
          </AuthProvider>
        </UsersDBProvider>
      </HashRouter>
    </>
  )
}
export interface IUser {
    username: string,
    alias: string,
    description: string,
    rol: Rol,
}

export enum Rol {
    Admin = 'admin',
    User = 'user',
}

export const usersData:IUser[] = [
    {
        username: 'Steve',
        alias: 'Capitan America',
        description: 'I am Steve Rogers',
        rol: Rol.User,
    },
    {
        username: 'Tony',
        alias: 'Iron Man',
        description: 'I am Tony Stark',
        rol: Rol.Admin,
    },
    {
        username: 'Bruce',
        alias: 'Hulk',
        description: 'I am Bruce Banner',
        rol: Rol.User,
    },
    {
        username: 'Hank',
        alias: 'Hombre Hormiga',
        description: 'I am Hank Pym',
        rol: Rol.Admin,
    }
]
import { useState, useEffect, FC, PropsWithChildren } from 'react';
import { createContext } from "react";
import { IUser, usersData } from "../data/usersData";

interface UsersDB {
    users : IUser[];
    findUser: (username:string) => IUser | undefined;
    updateUser: (user: Partial<IUser>)=>void
}

const UsersDBContext = createContext({} as UsersDB );

const UsersDBProvider:FC<PropsWithChildren> = ({ children }) => {

    const [users, setUsers] = useState([] as UsersDB['users']);

    useEffect(()=>{
        setUsers(usersData);
    }, [])

    const findUser = (username: string) => {
        return users.find(user=>user.username.toLowerCase() === username.toLowerCase());
    }

    const updateUser = (user:Partial<IUser>) => {
        const idx = users.findIndex((u) => u.username === user.username);
        if(idx >= 0){
            const newUsers = [...users];
            newUsers[idx] = { ...newUsers[idx], ...user }
            setUsers(newUsers);
        }
    }

    return(
        <UsersDBContext.Provider value={{ users, findUser, updateUser}}>
            { children }
        </UsersDBContext.Provider>
    )
}

export { UsersDBContext, UsersDBProvider}
import { useContext } from "react"
import { UsersDBContext } from "../store/UsersStore"

const useUsersDB = () => {
    const usersDB = useContext(UsersDBContext);
    return usersDB;
}

export { useUsersDB }
import { useState, FormEvent } from "react";
import { useParams } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";
import { Rol } from "../data/usersData";
import { useUsersDB } from "../hooks/useUsersDB";

type IProfileParams = {
    username: string
}

const ProfilePage = () => {

    const { username } = useParams<IProfileParams>();
    const { user: userAuth } = useAuth();   
    const usersDB = useUsersDB();
    const [edit, setEdit] = useState(false);
    
    const userProfile = usersDB.findUser(username as string);
    
    const [description, setDescription] = useState(userProfile?.description || '');

    const onToggleEdit = () => {
        setEdit(!edit)
    }

    const onSave = (e:FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        usersDB.updateUser({ ...userProfile, description});
        setEdit(false);
    }

    if (!userProfile) return <p>sorry! {username} isn't a logged user.</p>

    return(
        <>
            <h1>Perfil</h1>
            <p>Welcome, { userProfile?.username?.split('').map((elem, i)=>i===0?elem.toUpperCase():elem).join('') } </p>
            <p>Nombre de héroe: { userProfile?.alias }</p>
            {edit?(
                <form onSubmit={onSave}>
                    <span>Descripion: </span>
                    <input type="text" value={description} onChange={(e)=>setDescription(e.target.value)} />
                    <button>Guardar</button>
                </form>
            ):(
                <>
                    <p>Descripción: { userProfile.description }</p>
                    <p>Rol: { userProfile.alias }</p>
                </>
            )}
            {(userAuth?.username === userProfile?.username || userAuth?.rol === Rol.Admin) && (
                <button onClick={onToggleEdit} style={{ color: 'red', marginTop: '4px' }}>{edit? 'Cancelar':' Editar'}</button>
            )}
        </>
    )
}

export { ProfilePage }

UFFFFFFFFFFFFFFFFFFF! DESPUES DE 3 DIAS! Tuve que refactorizar todo, estaba muy perdido. Entonces comenze un nuevo proyecto desde 0.

Implemente una pagina Profiles que solo tiene acceso el rol admin. apartir de esto una ruta publica de profiles/:user para ver el perfil de un usuario.

En resumen: CREE LAS FUNCIONALIDADES DE:
-Crear un usuario.
-Editar un usuario
-Ver la informacion del usuario
-Crear un blog
-Editar un blog

Dejo el repositorio. Me quedan los estilos y proteger algunas rutas. pero esta es mi forma de solucionar hasta el momento los retos dados hasta ahora:

https://github.com/TheJB4/proyect-blog-react-router-thejb29

Que mas… pues nada, se me complico un poco implementar el componente ProtectedRoute y algunas protecciones son dadads en el mismo componente.

Condiciones:

  1. La persona debe estar autenticada.
  2. Si es dueño de la cuenta; puede ver el botón de edit profile en su perfil.
  3. Si el usuario está autenticado y viaja a la ruta de otro perfil no podrá ver el botón de edit profile, a menos que este usuario autenticado sea un admin.

Para realizar el reto:
Cambié la ruta del componente <ProfilePage> a una ruta dinámica:

 {
          path: "/profile/:slug",
          element:(
            <AuthRoute>
              <ProfilePage/>
            </AuthRoute>), 
        },

En mi custom hook useAuth.jsx tengo los roles de los usuarios y manejo el estado del username logueado. Los envío en el return del hook.


Ahora debo compartir estos datos al componente <Menu> y al componente <ProfilePage>; esto lo hago con el context del outlet y con las props para el <Menu>. Todos estos componentes están contenidos en un único componente <Layout>

Bueno, ahora hay que decirle al menú que el link que lleva a ProfilePage es dinámico; se hará de esta manera, uso una constante donde guardo la ruta con el dato dinámico según el usuario logueado y luego se la paso como valor al parámetro “to” del objeto que contiene la información del link a ProfilePage.

Ahora finalmente en el componente <ProfilePage> se debe renderizar un botón de edit profile dependiendo si el usuario es el dueño de la cuenta o no. Se haría de esta manera:
Guardo el parámetro y traigo los datos del contexto, luego, valido si el rol del usuario autenticado es un admin o no. Ahora con estos datos renderizo o no el saludo de la primera etiqueta p y la información de la etiqueta button validando si es o no dueño de la cuenta o admin el usuario autenticado gracias a la dirección de la ruta.

Así se ve cuando es un usuario en su cuenta:

Así si ve si navega a otro perfil:

Espero haber entendido lo que se pedía en el reto. Pero queda algo pendiente, hacer que el botón actualice datos. La cosa es que mi información es hardcodeada en su mayoría ya que no uso un objeto literal que tenga todos esos datos y luego pueda cambiar sus propiedades, tampoco uso una API, base de datos o localstorage. Pero si tuviera la forma de cambiar los datos que está debajo de Count information:

<h3>Count information</h3>
 <p><b>Interests:</b></p>
 <p>....</p>
 <p><b>Posts created:</b><span>{` ...`}</span></p>
 <p><b>Contributions:</b><span>{` ...`}</span></p>

Sería actualizando un estado que contenga un objeto literal con la información de ese perfil. La actualización sería con un componente formulario que actualice el estado y luego navegue al profilepage, el profilpage renderizaría datos nuevos.

Saludos, playzinautas

les comparto un proyecto que hice con los retos de las clases, me faltan detalles por mejorar, pero espero con ansias su feedback
Repo.
GitHub Page.

usuario admin clave admin123
usuario con role creator
juandc clave admin123

usuarios con role editor
nicobytes clave admin123
diannerd clave123

se me ocurrió separar los componentes del blog, y hacer lo mismo que hicimos en las rutas cuando usamos “AuthRoute” pero lo tome como un AuthAccess con el valido los posibles roles y ya sé que componente mostrar y cual no, pero me parece que se complica ya que se debe generar uno para cada componente

Aquí mi repositorio de lo que hice se pueden editar y eliminar los blogs solo si tienes los permisos se puede acceder a el de forma publica cree un custom hook para la info de los blogs, acciones y comparto la información en base a los patrones de render del curso anterior no se si es exactamente lo que se buscaba pero me gusto como quedo

Aqui mi solucion, como entendi el ejercicio auth.js ```js //cree un array de usuarios, estos tienen un array con sus respectivos permisos const usersList = [ { name: "jose", permissions: ["admin"] }, { name: "nani", permissions: ["admin"] }, { name: "over", permissions: [""] }, ]; function AuthProvider({ children }) { ... const login = ({ username }) => { //actualice la logica para que funcione const isAdmin = usersList.find( (admin) => admin.name === username && admin.permissions.includes("admin") ); setUser({ username, isAdmin }); navigate(from, { replace: true }); }; ```App.js ```js //Guarde los post en un estado const [posts, setPosts] = useState(blogData); ... //cree una nueva ruta, enviando el :slug el cual usare para saber que post debo editar { <Route path="/edit/:slug" element={ //agrego autenticaion a la ruta pues esta va a ser privada <AuthRoute> //envio los post y el setPosts <EditPage posts={posts} setPosts={setPosts} /> </AuthRoute> } ></Route> } ```BlogPost.js ```js const canEditDelete = auth.user?.isAdmin || blogPost.author === auth.user?.username; ... {canEditDelete && ( <button onClick={() => deleteItem(blogPost.title)}> Eliminar blogpost </button> )} //agrego un nuevo boton, uso la misma funcion, pues sirve {canEditDelete && ( <Link to={`/edit/${blogPost.slug}`}>Editar</Link> )} ```Cree por completo la nueva page EditPage.js ```js import React from "react"; import { useNavigate, useParams } from "react-router-dom"; //obtento los posts de las props export default function EditPage({ posts, setPosts }) { //obtengo el slug de los params const { slug } = useParams(); const navigate = useNavigate(); function editPost(e) { e.preventDefault(); //busco dentro del estado posts, el post con el mismo slug const editPost = posts.findIndex((post) => post.slug === slug); //creo una copia de los posts const newPosts = [...posts]; //agrego el contenido de mi input al post encontrado newPosts[editPost].content = e.target.content.value; //actualizo mi estado setPosts(newPosts); //redirijo al post en editado navigate(`/blog/${slug}`); } return ( //ejecuto la funcion <form onSubmit={editPost}>

Edita el post

<textarea name="content" id=""></textarea> <button>Guardar cambios</button> </form> ); } ```

Inko App

.
Github: https://github.com/HaroldZS/Inko
Deploy: https://haroldzs.github.io/Inko/
Figma: https://www.figma.com/design/rQctmQRRb8TZKn5TxHdjNN/Inko?node-id=0-1&t=o3A5YyGRU67tf7vc-1
.
Hice algo similar para mi aplicación de blogpost Inko app.
.
Normalmente en la página de detalle de Blog BlogDetail, no es necesario estar autenticado para poder ver la información del Blog y los comentarios asociados al mismo.
.

.
Posteriormente, si estuviéramos autenticados a cualquier usuario se le permite comentar el blog mediante un pequeño form en la parte inferior de la aplicación.
.

.
Si se diera el caso de que el usuario autenticado es el mismo que hizo un comentario, entonces se le permitirá editar y borrar sus propios comentarios.
.

.
En caso de editar se utiliza el mismo form inferior utilizado para crear comentarios, sin embargo la función del botón de Enviar cambia a para guardar los cambios según haya algún comentario en proceso de edición.
.
Al hacer click en el botón de Editar del comentario, se nos mostrará temporáneamente un mensaje de Editing... que indica que ese mensaje está en proceso de edición. El texto original del mensaje se muestra en el formulario inferior y se puede modificar desde ahí. Posteriormente con el botón de Enviar se pueden guardar los cambios.
.

.

.

.
En caso de querer borrar el propio mensaje se debe hacer click en el botón de Eliminar del comentario.
.

.
Finalmente, si estamos autenticados como admin entonces podremos borrar cualquier comentario.
.

.
Asimismo, los usuarios pueden borrar sus propios blogs y los admins pueden borrar cualquier blog.
.

.

.
La forma en que se logró esto fue de la siguiente manera:
.

import React, { useState } from "react";
import { useParams } from "react-router-dom";
import send from "../assets/send.svg";
import edit from "../assets/edit.svg";
import { getRandomId } from "../utils/getRandomId";

const commentInitialState = (user) => ({
  text: "",
  author: user?.name,
  authorId: user?.id,
  image: user?.image,
  id: "",
});

function BlogDetail({ getUsers, getAuth, setAuth, updateUsers }) {
  const [editingId, setEditingId] = useState(null);
  const { slug: blogId } = useParams();
  const users = getUsers();
  const user = getAuth();
  const blogs = users.map((user) => user.blogs).flat();
  const findBlog = blogs.find((blog) => blog.id === blogId);
  const [commentPayload, setCommentPayload] = useState(
    commentInitialState(user),
  );

  const resetComment = () => {
    setCommentPayload(commentInitialState(user));
  };

  const setComment = (e) => {
    setCommentPayload({
      ...commentPayload,
      text: e.target.value,
    });
  };

  const onCommentSubmit = (e) => {
    e.preventDefault();
    const newComment = {
      ...commentPayload,
      id: getRandomId(),
    };
    findBlog.comments.push(newComment);
    const currentUser = users.find((item) => item.id === user.id);
    Object.assign(user, currentUser);
    setAuth(user);
    updateUsers(users);
    resetComment();
  };

  const deleteComment = (id) => {
    const index = findBlog.comments.findIndex((comment) => comment.id === id);
    findBlog.comments.splice(index, 1);
    const currentUser = users.find((item) => item.id === user.id);
    Object.assign(user, currentUser);
    setAuth(user);
    updateUsers(users);
  };

  const editComment = (id, lastComment) => {
    setEditingId(id);
    setCommentPayload({
      ...lastComment,
    });
  };

  const onEditSubmit = (e) => {
    e.preventDefault();
    const lastComment = findBlog.comments.find(
      (comment) => comment.id === editingId,
    );
    Object.assign(lastComment, commentPayload);
    const currentUser = users.find((item) => item.id === user.id);
    Object.assign(user, currentUser);
    setAuth(user);
    updateUsers(users);
    setEditingId(null);
    resetComment();
  };

  return (
    <>
      <div className="flex justify-center pt-[45px]">
        <div className="mb-[24px] flex w-[343px] flex-col rounded-[8px] border-[0.5px] border-[#EEEEEE]/20 bg-[#31363F] p-2">
          <div className="mb-[20px] flex">
            <img
              src={findBlog.image}
              className="mr-4 h-[35px] w-[35px] rounded-full border-2 border-[#EEEEEE]/20"
              alt="user"
            />
            <div>
              <p className="text-[12px] font-semibold text-[#76ABAE]">
                {findBlog.title}
              </p>
              <p className="text-[10px] font-medium text-[#EEEEEE]">
                {findBlog.subTitle}
              </p>
            </div>
          </div>
          <p
            className="px-2 pb-2 text-[14px] font-medium text-[#EEEEEE]"
            style={{ whiteSpace: "pre-line" }}
          >
            {findBlog.description}
          </p>
        </div>
      </div>

      {findBlog?.comments?.length > 0 && (
        <div className="mx-auto mb-[71px] flex w-[375px] flex-col gap-4 px-4 pt-[24px]">
          {findBlog?.comments.map((comment, index) => (
            <div
              className={`relative flex w-fit gap-4 ${index % 2 !== 0 && "self-end"} rounded-[8px] border-[0.5px] border-[#EEEEEE]/20 bg-[#31363F] p-2 ${comment.authorId === user.id && "pb-1 pr-1"}`}
              key={comment.id}
            >
              {(comment.authorId === user.id || user.role === "admin") && (
                <div
                  className="absolute right-[-9px] top-[-9px] flex h-[18px] w-[18px] cursor-pointer items-center justify-center rounded-[8px] border-[0.5px] border-[#EEEEEE]/20 bg-[#76ABAE] text-[10px] font-medium text-[#EEEEEE]"
                  onClick={(e) => {
                    e.stopPropagation();
                    deleteComment(comment.id);
                  }}
                >
                  X
                </div>
              )}
              <img
                src={comment.image}
                className="h-[35px] w-[35px] rounded-full border-2 border-[#EEEEEE]/20"
                alt="user"
              />
              <div>
                <p className="text-[12px] font-semibold text-[#76ABAE]">
                  {comment.author}
                </p>
                <p className="pb-3 pr-2 text-[10px] font-medium text-[#EEEEEE]">
                  {editingId === comment.id ? "Editing..." : comment.text}
                </p>
              </div>
              {comment.authorId === user.id && (
                <img
                  className="h-[18px] w-[18px] self-end"
                  src={edit}
                  alt="edit"
                  onClick={() => editComment(comment.id, comment)}
                />
              )}
            </div>
          ))}
        </div>
      )}

      {user?.image && (
        <form
          className="fixed bottom-0 h-[55px] w-full bg-[#31363F]"
          onSubmit={!editingId ? onCommentSubmit : onEditSubmit}
        >
          <div className="absolute bottom-[12px] flex w-full justify-center gap-4 px-4">
            <img
              src={user.image}
              className="h-[23px] w-[23px] rounded-full border-2 border-[#EEEEEE]/20"
              alt="user"
            />
            <input
              type="text"
              className="h-[23px] w-[263px] rounded-[6px] border-[0.5px] border-[#EEEEEE]/20 bg-transparent pl-2 text-[10px] font-medium text-[#EEEEEE] placeholder-[#EEEEEE] placeholder:text-[10px] focus:outline-none"
              placeholder="Share your opinion"
              value={commentPayload.text}
              onChange={setComment}
            />
            <button className="flex h-[23px] w-[25px] items-center justify-center rounded-[8px] bg-[#76ABAE]">
              <img className="h-[18px] w-[18px]" src={send} alt="send" />
            </button>
          </div>
        </form>
      )}
    </>
  );
}

export { BlogDetail };

.
Mediante composición de componentes mi componente BlogDetail recibe los 2 estados getUsers y getAuth ademas de sus respectivos actualizadores updateUsers y setAuth.
.
En realidad getUsers es una función que me devuelve el estado de los usuarios en un array de objetos de usuarios. Los usuarios a su vez tienen una lista de blogs, y estos tienen una lista de comentarios.
.
Lo mismo para getAuth que en este caso devuelve un único objeto usuario, que es el que se encuentra autenticado en ese momento.
.
Entonces los usuarios los guardo en una constante users y el usuario autenticado lo guardo en user desde sus respectivas funciones. Ademas de recibir mediante useParams el id del blog para después obtener la información del blog en particular buscándolo en los blogs de users, así que en findBlog guardaré el objeto de la información del blog.
.

...
  const { slug: blogId } = useParams();
  const users = getUsers();
  const user = getAuth();
  const blogs = users.map((user) => user.blogs).flat();
  const findBlog = blogs.find((blog) => blog.id === blogId);
...

.
Para mostrar el formulario lo único que hago es verificar que si el usuario está autenticado entonces debería tener una imagen, mientras que si no está autenticado user debería ser un objeto vació {} por lo que no tendría una imagen. Según ello renderizo o no el formulario para crear y editar comentarios.
.

...
{user?.image && (
        <form
          className="fixed bottom-0 h-[55px] w-full bg-[#31363F]"
          onSubmit={!editingId ? onCommentSubmit : onEditSubmit}
        >
        ...
        </form>
)}
...

.
En caso de que el blog tenga algún comentario, puedo verificar si el id del usuario que escribió el comentario comment.authotId corresponde con el id del usuario atenticado user.id, o si el rol del usuario autenticado user.role es admin. Entonces renderizo el botón de eliminar comentario.
.

...
{(comment.authorId === user.id || user.role === "admin") && (
	<div
		className="absolute right-[-9px] top-[-9px] flex h-[18px] w-[18px] cursor-pointer items-center justify-center rounded-[8px] border-[0.5px] border-[#EEEEEE]/20 bg-[#76ABAE] text-[10px] font-medium text-[#EEEEEE]"
		onClick={(e) => {
			e.stopPropagation();
			deleteComment(comment.id);
		}}
	>
	X
	</div>
)}
...

.
Lo mismo pasa con el botón de editar comentarios. Aunque en este caso no vi necesario que el admin pueda editar los comentarios de los demás usuarios.
.

{comment.authorId === user.id && (
	<img
		className="h-[18px] w-[18px] self-end"
		src={edit}
		alt="edit"
		onClick={() => editComment(comment.id, comment)}
	/>
)}

.
Finalmente, en el form puesto que tengo 2 propositos, uno para crear comentarios y el otro para editar; lo que hago es preguntar si algún comentario está en proceso de edición. Por medio del editingId utilizaré una función u otra según la situación.
.
El estado editingId guardará el Id del comentario que se está editando, en caso de que no se esté editando algún comentario entonces será null.
.
El botón de editar comentario lo que hace es llamar a la función editComment, el cual guarda el id del comentario que se quiere editar y además remplaza el texto del input del formulario con el texto del comentario que queremos editar lastComment.
.
El texto que se renderiza del comentario también dependerá de si editingId tiene un id guardado, en caso de que haya un comentario en proceso de edición se renderiza por defecto Editing..., caso contrario se renderiza el texto del comentario.
.
Finalmente, al momento de editar un mensaje por medio de la función onEditSubmit se restablece el valor de editingId en null, para habilitar nuevamente la creación de nuevos comentarios habiendo finalizado el proceso de edición de comentario.
.

...
const [editingId, setEditingId] = useState(null);
...
const editComment = (id, lastComment) => {
    setEditingId(id);
    setCommentPayload({
      ...lastComment,
    });
  };
...
const onEditSubmit = (e) => {
  e.preventDefault();
  const lastComment = findBlog.comments.find(
    (comment) => comment.id === editingId,
  );
  Object.assign(lastComment, commentPayload);
  const currentUser = users.find((item) => item.id === user.id);
  Object.assign(user, currentUser);
  setAuth(user);
  updateUsers(users);
  setEditingId(null);
};
...
{comment.authorId === user.id && (
	<img
		className="h-[18px] w-[18px] self-end"
		src={edit}
		alt="edit"
		onClick={() => editComment(comment.id, comment)}
	/>
)}
...
<p className="pb-3 pr-2 text-[10px] font-medium text-[#EEEEEE]">
	{editingId === comment.id ? "Editing..." : comment.text}
</p>
...
{user?.image && (
        <form
          className="fixed bottom-0 h-[55px] w-full bg-[#31363F]"
          onSubmit={!editingId ? onCommentSubmit : onEditSubmit}
        >
        ...
        </form>
)}
...

Reto: roles complejos

.
En platzi no hay una ruta genérica de profile genérica, sino que tiene una ruta de perfil para cada distinto usuario, y todas esas rutas son públicas. Es decir, cualquier persona esté logueada o no, sea o no la persona de ese perfil, puede ver la información pública de esa ruta.
.
Pero únicamente se puede editar esa información en esa ruta si eres la persona dueña de esa cuenta en la que estás. Esto implica que necesitamos rutas dinámicas, para ello utilizaremos el hook useParams para ver cual es el usuario del cual queremos ver la información.
.
Además si una persona está autenticada y coincide que es dueño de esa ruta, entonces deberá tener un botón o algo para poder editar esa información de esa ruta. Únicamente si es la dueña de esa cuenta. Siendo un usuario normal no debería poder editar la cuenta de otros usuarios, excepto si soy un administrador.
.
En caso de ser administrador debería poder ver ese botón de editar, borrar o lo que sea de el perfil de cualquier usuario.

Despues de varias horas intentando pude completar el reto claro me apoye en los comentarios de mis compañeros, no es el codigo mas elegante ni el mejor pero hice lo que mejor pude.
app con netlify
repositorio

en la app pueden entrar cualquier persona con un usuario pero si le das a ver mi perfil dira que no estas registrado, puedes ver el perfil de los que si estan registrados que son
yairrchh: admin,
freddier: admin,
juandc: author,
isbersuar: author,
diegox: reader,
pedrito01: reader.

cada uno tiene un perfil distinto y permisos distintos

El admin puede eliminar los blog, el author puede eliminar y crear uno nuevo, el reder no tiene permisos ni de eliminar ni crear.

Hola comparto mi avance hasta el momento, para eliminar un post simplemente hice realice una condicional en donde se evalúa si es admi o el usuario que creo el post

<cod const canDelete = auth.user?.isAdmin || canEdit;

  const handleDelete = () => {
    if (window.confirm("¿Estás seguro de que deseas eliminar esta publicación?")) {
      const savedBlogdata = JSON.parse(localStorage.getItem("blogData"));
      const updatedBlogdata = savedBlogdata.filter((post) => post.slug !== slug);
      localStorage.setItem("blogData", JSON.stringify(updatedBlogdata));
      navigate("/", { replace: true });
    }
  };e> 

posteriormente en el botón eliminar

  {canDelete && (
            <button onClick={handleDelete}>Eliminar blog</button>
          )}

          <button onClick={() => navigate("/", { replace: true })}>Volver al blog</button>
        </>
      ) : (
        <p>Cargando...</p>

para actualizar el post la verdad fue un reto pues aun no entendía bien la lógica y estaba usando los datos siempre de blogdata y por lo tanto no se modificaban, a si que utilice directamente los datos de localstorage
simplemente en el blogdata al final agregue

localStorage.setItem('blogData', JSON.stringify(blogdata));
export{blogdata}

realice un componente EditPost


function  EditPost({editedContent,setEditedContent,onUpdate}) {
const[editedText,setEditedText]=useState(editedContent)
const navigate = useNavigate();
const{slug}=useParams();

  const handleTextChange = (event) => {
    setEditedContent(event.target.value);
  };
    const handleSave = () => {
      console.log('Contenido a actualizar:', editedContent);
         
       // setEditedContent(editedText);
        onUpdate(editedContent); // Pasar los cambios al componente padre
        navigate('/', { replace: true });
        console.log(JSON.parse(localStorage.getItem('blogData')));

      };
    
      const handleCancel = () => {
setEditedText(editedContent)      };
  return (
    <div>
    <textarea value={editedContent} onChange={handleTextChange} />
    <button onClick={handleSave}>Guardar</button>
    <button onClick={handleCancel}>Cancelar</button>
  </div>
  )
}

export {EditPost}

en el BlogPost
utilice un useEffect para obtener la lista de blogpost guardados en el localStorage , yt buscar el blogspot especifico el cual corresponda al slug en la URL

 useEffect(() => {
    const savedBlogdata = JSON.parse(localStorage.getItem("blogData"));
    const foundBlogpost = savedBlogdata.find((post) => post.slug === slug);

    if (foundBlogpost) {
      setEditedContent(foundBlogpost.content);
      setBlogpost(foundBlogpost);
    }
  }, [slug]);

posteriormente realice la logica para editar en la funcion

const handleUpdate = (updateContent) => {
    const updatedBlogdata = JSON.parse(localStorage.getItem("blogData")).map((post) =>
      post.slug === slug ? { ...post, content: updateContent } : post
    );
    localStorage.setItem("blogData", JSON.stringify(updatedBlogdata));
    setEditedContent(updateContent);
    setIsEditing(false);
  };

Si el usuario tiene permiso para editar (canEdit es verdadero) y está en modo de edición (isEditing es verdadero), se muestra el componente EditPost que permite al usuario editar el contenido del blogpost. Se le pasa el contenido actual y la función onUpdate para guardar los cambios.

 {canEdit && (
            <div>
              {isEditing ? (
                <EditPost
                  editedContent={editedContent}
                  setEditedContent={setEditedContent}
                  onUpdate={handleUpdate}
                />
              ) : (
                <button onClick={() => setIsEditing(true)}>Editar blog</button>
              )}
            </div>e> 

La base de mi solución fue convertir la pagina del profile en una ruta dinamica pasando el parametro “username” a la url y despues obteniendolo con useParams en la pagina del profile.
.
Creé una pagina para registrarse y crear un nuevo usuario y esos usuarios se almacenan en el local storage. De ahí traigo la informacion para mostrarla en la pagina del profile.
.
En esta pagina del profile hice una serie de condiciones para mostrar un componente u otro según si el usuario está logeado o no, si está logeado pero no es el usuario del que quiere ver la información y una condición para cuando el usuario está logeado y esta entrando a su propio perfil

App.js

import { HashRouter, Routes, Route } from "react-router-dom";

import { Menu } from "./Menu";
import { HomePage } from "./HomePage";
import { BlogPage } from "./BlogPage";
import { BlogPost } from "./BlogPost";
import { ProfilePage } from "./ProfilePage";
import { LoginPage } from "./LoginPage";
import { LogoutPage } from "./LogoutPage";
import { AuthProvider, AuthRoute } from "./Context/auth";
import { DataProvider } from "./Context/DataContext";

function App() {
  return (
    <>
      <HashRouter>
        <AuthProvider>
          <DataProvider>
            <Menu />

            <Routes>
              <Route path="/" element={<HomePage />} />
              <Route path="/blog" element={<BlogPage />}>
                <Route path=":slug" element={<BlogPost />} />
              </Route>
              <Route path="/login" element={<LoginPage />} />
              <Route
                path="/logout"
                element={
                  <AuthRoute>
                    <LogoutPage />
                  </AuthRoute>
                }
              />
              {/* <Route
                path="/profile"
                element={
                  <AuthRoute>
                    <ProfilePage />
                  </AuthRoute>
                }
              /> */}
              <Route path="/profile" element={<ProfilePage />}>
                <Route path=":slug" element={<ProfilePage />} />
              </Route>
              <Route path="*" element={<p>Not found!</p>} />
            </Routes>

            <footer></footer>
          </DataProvider>
        </AuthProvider>
      </HashRouter>
    </>
  );
}

export default App;

blogdata.js

const roles = {
  admin: { read: true, write: true, delete: true, type: "admin" },
  editor: { read: true, write: true, delete: true, type: "editor" },
  student: { read: true, write: true, delete: false, type: "student" },
};

const users = [
  { username: "luisrangelc", rol: roles.admin, name: "Luis Rangel" },
  { username: "kennethrc", rol: roles.admin, name: "Kenneth Rangel" },
  { username: "rociorangelp", rol: roles.student, name: "Rocio Rangel" },
  { username: "rodrigorangelp", rol: roles.student, name: "Rodrigo Rangel" },
];

export { users, roles };

ProfilePage.js

import React, { useEffect, useState } from "react";

import { useAuth } from "./Context/auth";
import { useParams, useNavigate } from "react-router-dom";

const ProfilePage = () => {
  const navigate = useNavigate();
  const auth = useAuth();
  const { slug } = useParams();

  const [user, setUser] = useState(auth.user ?? {});

  useEffect(() => {
    let redirectToLogin = false;
    if (slug) {
      redirectToLogin = !auth.existsUser(slug);
    } else if (!auth.user) {
      redirectToLogin = true;
    }
    if (redirectToLogin) {
      navigate("/login");
      return;
    } else {
      setUser(auth.user ?? auth.getPublicData(slug));
    }
  }, []);

  return (
    <>
      <h1>Profile Page</h1>
      <h2>{`Welcome, ${user.username}!!!`}</h2>
      <h3>{`Name: ${user.name}`}</h3>
      {auth.user && <h3>{`Rol: ${user.rol?.type}`}</h3>}
      {auth.user && <h3>{`Is Admin: ${user.isAdmin}`}</h3>}
    </>
  );
};

export { ProfilePage };

auth.js

import React from "react";
import { useNavigate, Navigate } from "react-router-dom";

import { users, roles } from "../blogdata";

const AuthContext = React.createContext();

function AuthProvider({ children }) {
  const navigate = useNavigate();
  const [user, setUser] = React.useState(null);

  const login = ({ username, state }) => {
    const isAdmin = users.find(
      (user) => user.username === username && user.rol === roles.admin
    )
      ? true
      : false;
    let userData = { name: "", rol: roles.student };
    if (existsUser(username)) {
      userData = getPublicData(username);
    }
    setUser({ username, isAdmin, ...userData });

    if (state) {
      navigate(state.prevPath);
      delete state.prevPath;
      return;
    }
    navigate("/profile");
  };

  const logout = () => {
    setUser(null);
    navigate("/login");
  };

  const existsUser = (username) => {
    return users.find((user) => user.username === username) ? true : false;
  };

  const getPublicData = (username) => {
    return users.find((user) => user.username === username);
  };

  const auth = { user, login, logout, existsUser, getPublicData };

  return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
}

function useAuth() {
  const auth = React.useContext(AuthContext);
  return auth;
}

function AuthRoute(props) {
  const auth = useAuth();

  if (!auth.user) {
    return <Navigate to="/login" />;
  }

  return props.children;
}

export { AuthProvider, useAuth, AuthRoute };