Merge pull request #88 from ricardo-campos-org/fix/87-list-of-bugs-in-the-frontend

fix: list of bugs in the frontend
This commit is contained in:
2024-10-06 19:12:57 -03:00
committed by GitHub
17 changed files with 373 additions and 86 deletions
+18 -12
View File
@@ -1,19 +1,21 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { act, render, screen } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import { describe, it, expect, vi } from 'vitest';
import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest';
import authContextMock from '../__mocks__/authContextMock';
import Footer from '../../components/Footer';
import AuthContext from '../../context/AuthContext';
// Mock necessary dependencies
vi.mock('../../env', () => ({
env: {
VITE_BUILD: 'test-build',
},
}));
describe('Footer component test', () => {
beforeAll(() => {
//import.meta.env.VITE_BUILD = ;
vi.stubEnv('VITE_BUILD', 'test-build');
});
afterAll(() => {
vi.unstubAllEnvs();
});
it('should renders the footer with current year and build version', () => {
render(
<BrowserRouter>
@@ -27,7 +29,7 @@ describe('Footer component test', () => {
const footerText = screen.getByTestId('footer-text');
expect(footerText).toBeDefined();
expect(footerText.innerHTML).toBe(`TaskNote App ©${currentYear} (test-build)`);
expect(footerText.innerHTML).toBe(`TaskNote App © ${currentYear} (Build test-build)`);
});
it('should renders the user email when logged in', () => {
@@ -63,8 +65,12 @@ describe('Footer component test', () => {
</AuthContext.Provider>
</BrowserRouter>,
);
const logoutButton = getByText('Logout');
logoutButton.click();
expect(signOutMock).toHaveBeenCalledTimes(1);
act(() => {
logoutButton.click();
expect(signOutMock).toHaveBeenCalledTimes(1);
});
});
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

+16 -9
View File
@@ -3,7 +3,6 @@ import {
Button, Col, Container, Row
} from 'react-bootstrap';
import { useNavigate } from 'react-router-dom';
import { env } from '../../env';
import AuthContext from '../../context/AuthContext';
import './style.css';
@@ -11,15 +10,14 @@ import './style.css';
* Footer component.
*
* This component provides the footer section of the application,
* providing navigation to logout.
* It also includes the build version.
* including logout functionality and version information.
*
* @returns {JSX.Element} The Footer component.
*/
function Footer(): JSX.Element {
const { signOut, user } = useContext(AuthContext);
const navigate = useNavigate();
const build = env.VITE_BUILD;
const build = import.meta.env.VITE_BUILD;
const currentYear = new Date().getFullYear();
const goOut = () => {
@@ -31,21 +29,30 @@ function Footer(): JSX.Element {
<footer className="footer">
<Container>
<Row className="align-items-center">
<Col xs={12} sm={4} className="text-center text-sm-start">
<Col xs={12} sm={4} className="text-center text-md-start">
<span data-testid="footer-text">
TaskNote App &copy;
{' '}
{currentYear}
{' '}
(
(Build
{' '}
{build}
)
</span>
</Col>
<Col xs={12} sm={4} className="text-center text-sm-end">
<Col xs={12} sm={4} className="text-center my-2 my-md-0">
{user?.email}
</Col>
<Col xs={12} sm={4} className="text-center text-sm-end">
<Button type="button" variant="link" onClick={() => goOut()} className="logout-button">
<Col xs={12} sm={4} className="text-center text-md-end">
<Button
type="button"
variant="link"
onClick={() => goOut()}
className="logout-button"
>
Logout
</Button>
</Col>
+30 -11
View File
@@ -2,18 +2,37 @@
background-color: #f8f9fa;
padding: 20px 0;
margin-top: 50px;
}
border-top: 1px solid #dee2e6;
.link {
color: #007bff;
text-decoration: none;
}
.footer-link {
color: #007bff;
font-weight: 500;
text-decoration: none;
transition: color 0.2s ease-in-out;
.link:hover {
text-decoration: underline;
}
&:hover {
color: #0056b3;
text-decoration: underline;
}
}
.logout-button {
color: #dc3545!important;
text-decoration: none!important;
.logout-button {
color: #dc3545 !important;
text-decoration: none !important;
&:hover {
text-decoration: underline !important;
}
}
@media (max-width: 768px) {
.footer-link {
display: block;
margin-top: 10px;
}
.logout-button {
margin-top: 10px;
}
}
}
+12 -7
View File
@@ -1,11 +1,16 @@
//$theme-colors: (
// 'info': tomato,
// 'danger': teal,
// 'primary': green,
//);
$theme-colors: (
'info': tomato,
'danger': teal,
'primary': #61cd8d,
);
@import '~bootstrap/scss/bootstrap';
.bg-body-secondary {
background-color: red;
$primary-color: #61cd8d;
$light-bg: #f8f9fa;
$dark-text: #343a40;
body {
background-color: $light-bg;
}
+2 -1
View File
@@ -1,6 +1,7 @@
type SummaryResponse = {
pendingTaskCount: number,
doneTaskCount: number
doneTaskCount: number,
notesCount: number
}
export type { SummaryResponse };
+75 -2
View File
@@ -1,11 +1,84 @@
import React from 'react';
import {
Card, Col, Container, Row
} from 'react-bootstrap';
/**
* About page component.
*
* This component displays information about the application,
* the technology used, and the developer.
*
* @returns {JSX.Element} The About page component.
*/
function About() {
function About(): JSX.Element {
return (
<h1>This is about!</h1>
<Container className="about-page my-5">
<Row className="justify-content-center mb-4">
<Col xs={12} md={10} lg={8}>
<Card className="p-4 shadow-sm">
<h2 className="text-center mb-4">About TaskNote</h2>
<p>
TaskNote is your go-to application for managing tasks and notes in one convenient
place. Whether you&apos;re keeping track of daily to-dos or organizing notes from
important meetings, TaskNote helps you stay organized and productive.
</p>
<h4 className="mt-4">Features</h4>
<ul>
<li>Quickly add and manage tasks and notes</li>
<li>Search and filter notes for easy access</li>
<li>Intuitive and clean user interface</li>
</ul>
<h4 className="mt-4">Help & How to Use</h4>
<p>
To get started, simply sign up or log in, and you&apos;ll have access to your
personalized dashboard. From there, you can create, edit, and delete tasks and notes,
and organize them however you like. Need assistance? Visit our Help page (in the
future) for tutorials and FAQs.
</p>
</Card>
</Col>
</Row>
<Row className="justify-content-center mb-4">
<Col xs={12} md={10} lg={8}>
<Card className="p-4 shadow-sm">
<h2 className="text-center mb-4">About the Technology</h2>
<p>
TaskNote is built using modern web technologies that ensure speed, reliability, and
security.
</p>
<ul>
<li>React with TypeScript for front-end development</li>
<li>Bootstrap 5 for responsive design and components</li>
<li>Spring Boot for backend services</li>
<li>PostgreSQL for database management</li>
<li>Docker for containerization and deployment</li>
<li>GitHub Actions for CI/CD, testing and linting enforcement</li>
<li>SonarCloud, and GitHub QL for security and improvements checks</li>
</ul>
</Card>
</Col>
</Row>
<Row className="justify-content-center">
<Col xs={12} md={10} lg={8}>
<Card className="p-4 shadow-sm">
<h2 className="text-center mb-4">About the Developer</h2>
<p>
Hi! I&apos;m Ricardo, the developer of TaskNote. I&apos;m
passionate about building applications that make life easier
and more organized. You can reach out to me at
<a href="mailto:ricardompcampos@gmail.com" className="text-decoration-none">
ricardompcampos@gmail.com
</a>
{' '}
for any questions or feedback.
</p>
</Card>
</Col>
</Row>
</Container>
);
}
+47
View File
@@ -0,0 +1,47 @@
.about-page {
.card {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
h2 {
font-size: 1.75rem;
font-weight: 600;
}
p, ul {
font-size: 1rem;
color: #333;
}
ul {
list-style-type: disc;
padding-left: 1.5rem;
}
a {
color: $primary-color;
&:hover {
text-decoration: underline;
}
}
}
.text-center {
text-align: center;
}
@media (max-width: 768px) {
.card {
padding: 20px;
}
h2 {
font-size: 1.5rem;
}
p, ul {
font-size: 0.95rem;
}
}
}
+10 -4
View File
@@ -34,7 +34,7 @@ function Home(): JSX.Element {
const getSummary = async () => {
try {
const response = await api.getJSON(`${ApiConfig.homeUrl}/summary`);
const response: SummaryResponse = await api.getJSON(`${ApiConfig.homeUrl}/summary`);
setSummary(response);
} catch (e) {
handleError(e);
@@ -93,6 +93,10 @@ function Home(): JSX.Element {
{summary?.pendingTaskCount && summary?.pendingTaskCount > 0
? 'Pending Tasks'
: 'No pending tasks!'}
<br />
{summary?.doneTaskCount && summary?.doneTaskCount > 0
? ` ${summary?.doneTaskCount} done tasks!`
: 'No done tasks!'}
</Card.Text>
<Button
variant="primary"
@@ -107,14 +111,16 @@ function Home(): JSX.Element {
<Col xs={12} md={6}>
<Card className="text-center h-100">
<Card.Header className="bg-success text-white">
<Card.Header className="bg-primary text-white">
Notes Summary
</Card.Header>
<Card.Body className="d-flex flex-column align-items-center justify-content-center">
<Card.Title className="display-4">10</Card.Title>
<Card.Title className="display-4">
{summary?.notesCount}
</Card.Title>
<Card.Text>Notes</Card.Text>
<Button
variant="success"
variant="primary"
type="button"
onClick={() => navigate('/notes')}
>
+5 -6
View File
@@ -1,8 +1,8 @@
import React, { useCallback, useContext, useEffect } from 'react';
import { Button, Container } from 'react-bootstrap';
import './styles.scss';
import { useNavigate } from 'react-router-dom';
import AuthContext from '../../context/AuthContext';
import './styles.scss';
/**
* Landing page component.
@@ -45,10 +45,10 @@ function Landing(): JSX.Element {
}, []);
return (
<Container fluid className="vh-100 d-flex justify-content-center align-items-center">
<div className="text-center">
<h1 className="display-4 fw-bold">Welcome to TaskNote App</h1>
<p className="lead mb-4">
<Container fluid className="vh-100 d-flex justify-content-center align-items-center landing-page">
<div>
<h1 className="display-4">Welcome to TaskNote</h1>
<p className="lead">
Your best friend to keep up with notes and tasks!
</p>
@@ -71,7 +71,6 @@ function Landing(): JSX.Element {
</Button>
</div>
</Container>
);
}
+61 -2
View File
@@ -1,3 +1,62 @@
body {
background-color: #ccc;
$primary-color: #61cd8d;
$light-bg: #f8f9fa;
$dark-text: #343a40;
.landing-page {
position: relative;
text-align: center;
color: $dark-text;
background: url('../../assets/landing-bg.jpg') no-repeat center center;
background-size: cover; // Ensures the image covers the whole background
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.85); // Slight white overlay for text readability
z-index: 1;
}
div {
position: relative;
z-index: 2; // Ensures the content is above the background and overlay
}
h1 {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 1rem;
color: $dark-text;
}
p {
font-size: 1.25rem;
margin-bottom: 2rem;
color: $dark-text;
}
.btn-lg {
font-size: 1rem;
padding: 0.75rem 1.5rem;
}
.btn-primary {
background-color: $primary-color;
border-color: $primary-color;
&:hover {
background-color: darken($primary-color, 10%);
}
}
.btn-outline-primary {
color: $primary-color;
border-color: $primary-color;
&:hover {
background-color: $primary-color;
color: #fff;
}
}
}
+14 -12
View File
@@ -4,8 +4,9 @@ import React, {
import {
Alert, Button, Card, Col, Container, Form, Row
} from 'react-bootstrap';
import { useNavigate } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';
import AuthContext from '../../context/AuthContext';
import './styles.scss';
/**
* Login page component.
@@ -45,6 +46,7 @@ function Login(): JSX.Element {
const form = event.currentTarget;
if (form.checkValidity() === false) {
setFormInvalid(true);
setErrorMessage('Please fill in your username and password!');
return;
}
@@ -65,12 +67,12 @@ function Login(): JSX.Element {
useEffect(() => {}, [formInvalid]);
return (
<Container className="vh-100 d-flex justify-content-center align-items-center">
<Container fluid className="login-page">
<Row className="justify-content-center w-100">
<Col xs={12} md={6} lg={4}>
<Card>
<Card.Body className="p-4">
<h2 className="text-center mb-4">Login</h2>
<h2 className="text-center">Login</h2>
{formInvalid ? (
<Alert variant="danger">
@@ -80,12 +82,12 @@ function Login(): JSX.Element {
<Form noValidate validated={validated} onSubmit={handleSubmit}>
<Form.Group className="mb-3" controlId="formBasicEmail">
<Form.Label>Email address</Form.Label>
<Form.Label>Email</Form.Label>
<Form.Control
required
type="email"
name="email"
placeholder="Enter email"
placeholder="Type your email here"
/>
</Form.Group>
@@ -95,7 +97,7 @@ function Login(): JSX.Element {
required
type="password"
name="password"
placeholder="Password"
placeholder="Type your password here"
/>
</Form.Group>
@@ -109,16 +111,16 @@ function Login(): JSX.Element {
</Form>
<div className="text-center mt-3">
Don&apos;t have an account?&nbsp;
<a href="/register" className="text-decoration-none">
Register
</a>
Don&apos;t have an account yet?&nbsp;
<Link to="/register" className="text-decoration-none">
Register here
</Link>
</div>
<div className="text-center mt-3">
<a href="/" className="text-decoration-none">
<Link to="/" className="text-decoration-none">
Back to home
</a>
</Link>
</div>
</Card.Body>
</Card>
+62
View File
@@ -0,0 +1,62 @@
$primary-color: #61cd8d;
$light-bg: #f8f9fa;
$dark-text: #343a40;
.login-page {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: url('../../assets/landing-bg.jpg') no-repeat center center;
background-size: cover;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.85); // White overlay for readability
z-index: 1;
}
.card {
position: relative;
z-index: 2;
border: none;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
h2 {
font-size: 1.75rem;
font-weight: 600;
margin-bottom: 1.5rem;
color: $dark-text;
}
.form-control {
border-radius: 5px;
}
.btn-primary {
background-color: $primary-color;
border-color: $primary-color;
&:hover {
background-color: darken($primary-color, 10%);
}
}
.text-center a {
color: $primary-color;
&:hover {
text-decoration: underline;
}
}
.alert-danger {
background-color: rgba(255, 0, 0, 0.1);
border-color: rgba(255, 0, 0, 0.3);
}
}
+13 -16
View File
@@ -4,7 +4,7 @@ import React, {
import {
Alert, Button, Card, Col, Container, Form, Row
} from 'react-bootstrap';
import { useNavigate } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';
import AuthContext from '../../context/AuthContext';
/**
@@ -45,6 +45,7 @@ function Register(): JSX.Element {
const form = event.currentTarget;
if (form.checkValidity() === false) {
setFormInvalid(true);
setErrorMessage('Please fill in your username and password!');
return;
}
@@ -65,12 +66,12 @@ function Register(): JSX.Element {
useEffect(() => {}, [formInvalid]);
return (
<Container className="vh-100 d-flex justify-content-center align-items-center">
<Container fluid className="login-page">
<Row className="justify-content-center w-100">
<Col xs={12} md={6} lg={4}>
<Card>
<Card.Body className="p-4">
<h2 className="text-center mb-4">Create account</h2>
<h2 className="text-center">Create account</h2>
{formInvalid ? (
<Alert variant="danger">
@@ -80,12 +81,12 @@ function Register(): JSX.Element {
<Form noValidate validated={validated} onSubmit={handleSubmit}>
<Form.Group className="mb-3" controlId="formBasicEmail">
<Form.Label>Email address</Form.Label>
<Form.Label>Email</Form.Label>
<Form.Control
required
type="email"
name="email"
placeholder="Enter email"
placeholder="Type your email here"
/>
</Form.Group>
@@ -95,7 +96,7 @@ function Register(): JSX.Element {
required
type="password"
name="password"
placeholder="Password"
placeholder="Type your password here"
/>
</Form.Group>
@@ -104,25 +105,21 @@ function Register(): JSX.Element {
type="submit"
className="w-100"
>
Login
Sign-up and register
</Button>
</Form>
<div className="text-center mt-3">
Already have an account?&nbsp;
<a href="/login" className="text-decoration-none">
Login
</a>
&nbsp;or&nbsp;
<a href="/" className="text-decoration-none">
Reset
</a>
<Link to="/login" className="text-decoration-none">
Go to Login
</Link>
</div>
<div className="text-center mt-3">
<a href="/" className="text-decoration-none">
<Link to="/" className="text-decoration-none">
Back to home
</a>
</Link>
</div>
</Card.Body>
</Card>
@@ -1,3 +1,4 @@
package br.com.tasknoteapp.server.response;
public record SummaryResponse(Integer pendingTaskCount, Integer doneTaskCount) {}
public record SummaryResponse(
Integer pendingTaskCount, Integer doneTaskCount, Integer notesCount) {}
@@ -31,11 +31,13 @@ class HomeServiceImpl implements HomeService {
public SummaryResponse getSummary() {
log.info("Getting summary");
List<TaskResponse> tasks = taskService.getAllTasks();
List<NoteResponse> notes = noteService.getAllNotes();
long doneCount = tasks.stream().filter(t -> t.done() == true).count();
long undoneCount = tasks.stream().filter(t -> t.done() == false).count();
int taskCount = notes.size();
return new SummaryResponse((int) undoneCount, (int) doneCount);
return new SummaryResponse((int) undoneCount, (int) doneCount, taskCount);
}
/**
@@ -35,7 +35,7 @@ class HomeControllerTest {
@DisplayName("Get summary happy path should succeed")
@WithMockUser(username = "user@domain.com", password = "abcde123456A@")
void getSummary_happyPath_shouldSucceed() throws Exception {
SummaryResponse response = new SummaryResponse(0, 6);
SummaryResponse response = new SummaryResponse(0, 6, 2);
when(homeService.getSummary()).thenReturn(response);
@@ -48,13 +48,14 @@ class HomeControllerTest {
.andExpect(status().isOk())
.andExpect(jsonPath("$.pendingTaskCount").value(response.pendingTaskCount()))
.andExpect(jsonPath("$.doneTaskCount").value(response.doneTaskCount()))
.andExpect(jsonPath("$.notesCount").value(response.notesCount()))
.andReturn();
}
@Test
@DisplayName("Get summary user not authorized should fail")
void getSummary_userNotAuthorized_shouldFail() throws Exception {
SummaryResponse response = new SummaryResponse(0, 6);
SummaryResponse response = new SummaryResponse(0, 6, 2);
when(homeService.getSummary()).thenReturn(response);