<file_sep>import React from 'react';
import './App.css';
import { Route, Switch } from 'wouter';
import { Container } from 'react-bulma-components';

import NavigationBar from './components/NavigationBar';

import Home from './views/Home';
import NewPost from './views/NewPost';
import ViewPost from './views/ViewPost';
import EditPost from './views/EditPost';

function App() {
  return (
    <div className="App">
      <NavigationBar />
      <Container mt="2" mb="5">
        <Switch>
          <Route path="/posts/:id/edit" component={EditPost} />
          <Route path="/posts/:id" component={ViewPost} />
          <Route path="/posts" component={Home} />
          <Route path="/new" component={NewPost} />
          <Route component={Home} />
        </Switch>
      </Container>
    </div>
  )
}

export default App;
<file_sep># React Blog
Un frontend (muy simple) que consume la API [JSONPlaceholder](https://jsonplaceholder.typicode.com/). Desarrollado con React y [react-bulma-components](https://www.npmjs.com/package/react-bulma-components).

Para ejecutar localmente, clonar el repositorio, instalar dependencias con `npm install`, y ejecutar el servidor de desarrollo con `npm run dev` (o usar `npm run build` para crear una build de producción)

<file_sep>import React, { useEffect, useState } from 'react';
import { Link } from 'wouter';
import { Box, Button, Heading } from 'react-bulma-components';
import Swal from 'sweetalert2';

import ErrorBox from '../components/ErrorBox';
import ApiClient from '../api/ApiClient';

function Home () {
  const [posts, setPosts] = useState([]);
  const [error, setError] = useState(null);

  useEffect(() => {
    ApiClient.getAllPosts().then((posts) => {
      setPosts(posts);
    }).catch((err) => {
      setError(err.message);
    });
  }, []);

  const toast = Swal.mixin({
    toast: true,
    position: 'top-end',
    showConfirmButton: false,
    timer: 3000,
    timerProgressBar: true,
    didOpen: (toast) => {
      toast.addEventListener('mouseenter', Swal.stopTimer)
      toast.addEventListener('mouseleave', Swal.resumeTimer)
    }
  });

  const handleDeletion = (id) => {
    Swal.fire({
      title: 'Delete this post?',
      showCancelButton: true,
      confirmButtonColor: '#f14668',
      confirmButtonText: 'Delete',
      cancelButtonColor: '#3e8ed0',
      cancelButtonText: `Cancel`,
      showLoaderOnConfirm: true,
      preConfirm: () => {
        return ApiClient.deletePost(id).catch((err) => {
          Swal.showValidationMessage(`Something went wrong: ${err.message}`)
        });
      }
    }).then((result) => {
      if (result.isConfirmed) {
        setPosts(posts.filter((post) => post.id !== id));
        toast.fire({
          icon: 'success',
          title: 'Post deleted'
        });
      }
    });
  }

  if (error) return (
    <ErrorBox message={error} />
  );

  return (
    <div>
      <Heading size="3">Posts</Heading>
      <div className="posts">
        {posts.map((post) => (
          <Box key={post.id}>
            <div style={{display: 'flex'}}>
              <div style={{display: 'flex', flex: 1}}>
                <Link href={`/posts/${post.id}`} style={{fontWeight: 'bold'}}>
                  {post.title}
                </Link>
              </div>
              <div style={{display: 'flex', gap: 3}}>
                <Button
                  size="small"
                  color="primary"
                  renderAs={Link}
                  href={`/posts/${post.id}`}
                >
                  View
                </Button>
                <Button
                  size="small"
                  color="info"
                  renderAs={Link}
                  href={`/posts/${post.id}/edit`}
                >
                  Edit
                </Button>
                <Button
                  size="small"
                  color="danger"
                  onClick={() => handleDeletion(post.id)}
                >
                  Delete
                </Button>
              </div>
            </div>
          </Box>
        ))}
      </div>
    </div>
  );
}

export default Home;
<file_sep>import React, { useEffect, useState } from 'react';
import { useRoute } from 'wouter';
import { Heading } from 'react-bulma-components';

import ErrorBox from '../components/ErrorBox';
import ApiClient from '../api/ApiClient';

function ViewPost () {
  const [match, params] = useRoute('/posts/:id');

  const [post, setPost] = useState(null);
  const [isLoading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    ApiClient.getPost(params.id).then((post) => {
      setPost(post);
    })
    .catch((err) => setError(err.message))
    .finally(() => setLoading(false));
  }, []);

  if (isLoading) return (
    <div>Loading...</div>
  );

  if (error) return (
    <ErrorBox message={error} />
  );

  if (post) return (
    <div className="postView">
      <Heading size="3">{post.title}</Heading>
      <p>
        {post.body}
      </p>
    </div>
  );

  return null;
}

export default ViewPost;
<file_sep>import React, { useState } from 'react';
import { Link } from 'wouter';
import {
  Button,
  Navbar
} from 'react-bulma-components';

function NavigationBar () {
  const [active, setActive] = useState(false);

  return (
    <Navbar color="info" active={active}>
      <Navbar.Brand>
        <Link href="/">
          <Navbar.Item>
            <span style={{fontWeight: 'bold'}}>React Blog</span>
          </Navbar.Item>
        </Link>
        <Navbar.Burger onClick={() => setActive(!active)} />
      </Navbar.Brand>
      <Navbar.Menu>
        <Navbar.Container>
          <Navbar.Item href="/" renderAs={Link}>
            Post listing
          </Navbar.Item>
        </Navbar.Container>
        <Navbar.Container align="right">
          <Navbar.Item renderAs="div">
            <Button color="primary" href="/new" renderAs={Link}>
              New Post
            </Button>
          </Navbar.Item>
        </Navbar.Container>
      </Navbar.Menu>
    </Navbar>
  )

}

export default NavigationBar;
<file_sep>import React from 'react';
import { Link } from 'wouter';
import { Box, Button, Heading } from 'react-bulma-components';

function ErrorBox ({message}) {
  return (
    <Box>
      <Heading size="4">Something went wrong</Heading>
      <p>{message}</p>
      <div style={{marginTop: 6, display: 'flex', gap: 6}}>
        <Button color="primary" onClick={() => window.location.reload()}>Reload</Button>
        <Button color="info" renderAs={Link} href="/">Home page</Button>        
      </div>
    </Box>
  );
}

export default ErrorBox;
